├── .gitattributes
├── .gitignore
├── README.md
├── authorization-server
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── org
│ │ └── arip
│ │ └── springmvc
│ │ └── oauth2
│ │ └── config
│ │ ├── AppConfig.java
│ │ ├── AuthorizationServerConfig.java
│ │ ├── SecurityConfig.java
│ │ └── core
│ │ ├── DispatcherServletInitializer.java
│ │ └── SecurityInitializer.java
│ └── webapp
│ ├── index.html
│ └── login.jsp
├── implicit-client
├── .bowerrc
├── .gitignore
├── bower.json
├── gruntfile.js
├── package.json
└── src
│ └── main
│ ├── css
│ └── style.css
│ ├── index.html
│ └── js
│ └── app.js
└── resource-server
├── pom.xml
└── src
└── main
├── java
└── org
│ └── arip
│ └── springmvc
│ └── oauth2
│ ├── config
│ ├── AppConfig.java
│ ├── ResourceServerConfig.java
│ ├── SecurityConfig.java
│ └── core
│ │ ├── DispatcherServletInitializer.java
│ │ └── SecurityInitializer.java
│ ├── controller
│ └── ApiController.java
│ └── filter
│ └── CORSFilter.java
├── resources
└── clients.properties
└── webapp
└── index.html
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | target
3 | *.iml
4 | *.class
5 |
6 | # Mobile Tools for Java (J2ME)
7 | .mtj.tmp/
8 |
9 | # Package Files #
10 | *.jar
11 | *.war
12 | *.ear
13 |
14 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
15 | hs_err_pid*
16 |
17 | # =========================
18 | # Operating System Files
19 | # =========================
20 |
21 | # OSX
22 | # =========================
23 |
24 | .DS_Store
25 | .AppleDouble
26 | .LSOverride
27 |
28 | # Thumbnails
29 | ._*
30 |
31 | # Files that might appear in the root of a volume
32 | .DocumentRevisions-V100
33 | .fseventsd
34 | .Spotlight-V100
35 | .TemporaryItems
36 | .Trashes
37 | .VolumeIcon.icns
38 |
39 | # Directories potentially created on remote AFP share
40 | .AppleDB
41 | .AppleDesktop
42 | Network Trash Folder
43 | Temporary Items
44 | .apdisk
45 |
46 | # Windows
47 | # =========================
48 |
49 | # Windows image file caches
50 | Thumbs.db
51 | ehthumbs.db
52 |
53 | # Folder config file
54 | Desktop.ini
55 |
56 | # Recycle Bin used on file shares
57 | $RECYCLE.BIN/
58 |
59 | # Windows Installer files
60 | *.cab
61 | *.msi
62 | *.msm
63 | *.msp
64 |
65 | # Windows shortcuts
66 | *.lnk
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Spring MVC Security OAuth2 Example
2 | The OAuth 2.0 provider mechanism is responsible for exposing OAuth 2.0 protected resources.
3 | The configuration involves establishing the OAuth 2.0 clients that can access its protected
4 | resources independently or on behalf of a user. The provider does this by managing and
5 | verifying the OAuth 2.0 tokens used to access the protected resources. Where applicable,
6 | the provider must also supply an interface for the user to confirm that a client can
7 | be granted access to the protected resources (i.e. a confirmation page).
8 |
9 | The provider role in OAuth 2.0 is actually split between Authorization Service and
10 | Resource Service, and while these sometimes reside in the same application,
11 | with Spring Security OAuth you have the option to split them across two applications,
12 | and also to have multiple Resource Services that share an Authorization Service.
13 | The requests for the tokens are handled by Spring MVC controller endpoints, and access to
14 | protected resources is handled by standard Spring Security request filters.
15 |
16 | ### Prerequisite for run `authorization-server` and `resource-server`:
17 | * Installed Maven Project environment
18 |
19 | ### Prerequisite for run `implicit-client`
20 | * Installed [Node.js] (https://nodejs.org) and [npm] (https://www.npmjs.com/)
21 | * Installed [bower-cli] (http://bower.io/)
22 |
23 | ### Build and Run :
24 |
25 | * Run `authorization-server` and `resource-server` application :
26 |
27 | execute `mvn clean tomcat7:run`
28 |
29 | * Run `implicit-client` application, execute the following command :
30 |
31 | - execute `npm install`
32 | - execute `bower install`
33 | - execute `grunt`
34 |
35 |
36 | ### Grant Type : Resource Owner Password Credentials
37 |
38 | The resource owner password credentials (i.e., username and password) can be used directly
39 | as an authorization grant to obtain an access token. The credentials should only be used
40 | when there is a high degree of trust between the resource owner and the client (e.g., the
41 | client is part of the device operating system or a highly privileged application), and
42 | when other authorization grant types are not available (such as an authorization code).
43 |
44 | Even though this grant type requires direct client access to the resource owner credentials,
45 | the resource owner credentials are used for a single request and are exchanged for an access token.
46 | This grant type can eliminate the need for the client to store the resource owner credentials
47 | for future use, by exchanging the credentials with a long-lived access token or refresh token.
48 |
49 | +----------+
50 | | Resource |
51 | | Owner |
52 | | |
53 | +----------+
54 | v
55 | | Resource Owner
56 | (A) Password Credentials
57 | |
58 | v
59 | +---------+ +---------------+
60 | | |>--(B)---- Resource Owner ------->| |
61 | | | Password Credentials | Authorization |
62 | | Client | | Server |
63 | | |<--(C)---- Access Token ---------<| |
64 | | | (w/ Optional Refresh Token) | |
65 | +---------+ +---------------+
66 |
67 | Figure 1: Resource Owner Password Credentials Flow
68 |
69 | The following is how the Grant Type works in this application :
70 |
71 | * Request access token :
72 |
73 | curl -X POST -vu clientapp:123456 http://localhost:8080/authorization-server/oauth/token -H "Accept: application/json" -d "client_id=clientapp&grant_type=password&username=admin&password=passw0rd"
74 |
75 | * `auth-server` will give you JSON response with access token :
76 |
77 | {
78 | "access_token":"9b3456a4-c5db-422e-a422-883a60bf1899",
79 | "token_type":"bearer",
80 | "expires_in":43199,
81 | "scope":"read write"
82 | }
83 |
84 | * Access resource with header parameter :
85 |
86 | curl -H "Authorization: Bearer 9b3456a4-c5db-422e-a422-883a60bf1899" http://localhost:8081/resource-server/api/admin
87 |
88 | * `resource-server` will give JSON response :
89 |
90 | {
91 | "success":true,
92 | "page":"admin",
93 | "user":"admin"
94 | }
95 |
96 |
97 |
98 | ### Grant Type : Client Credentials
99 |
100 | The client can request an access token using only its client credentials (or other supported
101 | means of authentication) when the client is requesting access to the protected resources
102 | under its control, or those of another resource owner that have been previously arranged with
103 | the authorization server (the method of which is beyond the scope of this specification).
104 |
105 | The client credentials grant type MUST only be used by confidential clients.
106 |
107 | +---------+ +---------------+
108 | | | | |
109 | | |>--(A)- Client Authentication --->| Authorization |
110 | | Client | | Server |
111 | | |<--(B)---- Access Token ---------<| |
112 | | | | |
113 | +---------+ +---------------+
114 |
115 | Figure 2: Client Credentials Flow
116 |
117 | The following is how the Grant Type works in this application :
118 |
119 | * Request token with header `client_id` and `client_secret` as Basic Authorization and with `client_id` and `grant_type` as parameters.
120 |
121 | curl -X POST -vu clientcred:123456 http://localhost:8080/authorization-server/oauth/token -H "Accept: application/json" -d "client_id=clientcred&grant_type=client_credentials"
122 |
123 | * We will get JSON response :
124 |
125 | {
126 | "access_token":"67f262cb-55f6-4c60-a49e-ae0ab8a8438c",
127 | "token_type":"bearer",
128 | "expires_in":43199,
129 | "scope":"trust"
130 | }
131 |
132 | * Access resource with header parameter :
133 |
134 | curl -H "Authorization: Bearer 67f262cb-55f6-4c60-a49e-ae0ab8a8438c" http://localhost:8081/resource-server/api/client
135 |
136 | * If success, will get JSON response :
137 |
138 | {
139 | "sukses":true,
140 | "page":"client",
141 | "user":"clientcred"
142 | }
143 |
144 |
145 | ### Grant Type : Authorization Code
146 |
147 | The authorization code is obtained by using an authorization server as an intermediary
148 | between the client and resource owner. Instead of requesting authorization directly from
149 | the resource owner, the client directs the resource owner to an authorization server,
150 | which in turn directs the resource owner back to the client with the authorization code.
151 |
152 | Before directing the resource owner back to the client with the authorization code,
153 | the authorization server authenticates the resource owner and obtains authorization.
154 | Because the resource owner only authenticates with the authorization server, the resource
155 | owner's credentials are never shared with the client.
156 |
157 | The authorization code provides a few important security benefits, such as the ability to
158 | authenticate the client, as well as the transmission of the access token directly to
159 | the client without passing it through the resource owner's user-agent and potentially
160 | exposing it to others, including the resource owner.
161 |
162 | +----------+
163 | | Resource |
164 | | Owner |
165 | | |
166 | +----------+
167 | ^
168 | |
169 | (B)
170 | +----|-----+ Client Identifier +---------------+
171 | | -+----(A)-- & Redirection URI ---->| |
172 | | User- | | Authorization |
173 | | Agent -+----(B)-- User authenticates --->| Server |
174 | | | | |
175 | | -+----(C)-- Authorization Code ---<| |
176 | +-|----|---+ +---------------+
177 | | | ^ v
178 | (A) (C) | |
179 | | | | |
180 | ^ v | |
181 | +---------+ | |
182 | | |>---(D)-- Authorization Code ---------' |
183 | | Client | & Redirection URI |
184 | | | |
185 | | |<---(E)----- Access Token -------------------'
186 | +---------+ (w/ Optional Refresh Token)
187 |
188 | Note: The lines illustrating steps (A), (B), and (C) are broken into two parts
189 | as they pass through the user-agent.
190 |
191 | Figure 3: Authorization Code Flow
192 |
193 | The following is how the Grant Type works in this application :
194 |
195 | * Call this URL in browser :
196 |
197 | http://localhost:8080/authorization-server/oauth/authorize?client_id=clientauthcode&response_type=code&redirect_uri=http://localhost:8081/resource-server/api/state/new
198 |
199 | * You will redirected to login page, login with username=`admin` and password=`passw0rd` and choose approve radio button and click Autorize button.
200 |
201 | * You will redirected to redirect uri with parameter code :
202 |
203 | http://localhost:8081/resource-server/api/state/new?code=CODE
204 |
205 | * Exchange authorization code with access token with call request :
206 |
207 | curl -X POST -vu clientauthcode:123456 http://localhost:8080/authorization-server/oauth/token -H "Accept: application/json" -d "grant_type=authorization_code&code=CODE&redirect_uri=http://localhost:8081/resource-server/api/state/new"
208 |
209 | * We will get JSON response :
210 |
211 | {
212 | "access_token":"08664d93-41e3-473c-b5d2-f2b30afe7053",
213 | "token_type":"bearer",
214 | "refresh_token":"436761f1-2f26-412b-ab0f-bbf2cd7459c4",
215 | "expires_in":43199,
216 | "scope":"write read"
217 | }
218 |
219 | * Take access token to access protection resource, e.g :
220 |
221 | curl http://localhost:8081/resource-server/api/admin?access_token=08664d93-41e3-473c-b5d2-f2b30afe7053
222 |
223 | * In this case, `resource-server` will validation token to authorization :
224 |
225 | curl -X POST -vu clientauthcode:123456 http://localhost:8080/authorization-server/oauth/check_token?token=08664d93-41e3-473c-b5d2-f2b30afe7053
226 |
227 | * You will get JSON response :
228 |
229 | {
230 | "aud": ["arip"],
231 | "exp": 1444158090,
232 | "user_name": "admin",
233 | "authorities": "ADMIN",
234 | "client_id": "clientauthcode",
235 | "scope": ["read", "write"]
236 | }
237 |
238 |
239 | * Finaly, you will get resource :
240 |
241 | {
242 | "success":true,
243 | "page":"admin",
244 | "user":"admin"
245 | }
246 |
247 | * If access token expired, you can request refresh token :
248 |
249 | curl -X POST -vu clientauthcode:123456 http://localhost:8080/authorization-server/oauth/token -d "client_id=clientauthcode&grant_type=refresh_token&refresh_token=436761f1-2f26-412b-ab0f-bbf2cd7459c4"
250 |
251 | * `auth-server` will give you JSON response and new access token :
252 |
253 | {
254 | "access_token":"e425cee6-7167-4eea-91c3-2706d01dab7f",
255 | "token_type":"bearer",
256 | "refresh_token":"436761f1-2f26-412b-ab0f-bbf2cd7459c4",
257 | "expires_in":43199,"scope":"write read"
258 | }
259 |
260 |
261 | ### Grant Type : Implicit
262 |
263 | The implicit grant type is used to obtain access tokens (it does not support the issuance
264 | of refresh tokens) and is optimized for public clients known to operate a particular
265 | redirection URI. These clients are typically implemented in a browser using a scripting language
266 | such as JavaScript.
267 |
268 | Since this is a redirection-based flow, the client must be capable of interacting with
269 | the resource owner's user-agent (typically a web browser) and capable of receiving incoming
270 | requests (via redirection) from the authorization server. Unlike the authorization code
271 | grant type, in which the client makes separate requests for authorization and for an access
272 | token, the client receives the access token as the result of the authorization request.
273 |
274 | The implicit grant type does not include client authentication, and relies on the presence
275 | of the resource owner and the registration of the redirection URI. Because the access token
276 | is encoded into the redirection URI, it may be exposed to the resource owner and other
277 | applications residing on the same device.
278 |
279 | +----------+
280 | | Resource |
281 | | Owner |
282 | | |
283 | +----------+
284 | ^
285 | |
286 | (B)
287 | +----|-----+ Client Identifier +---------------+
288 | | -+----(A)-- & Redirection URI --->| |
289 | | User- | | Authorization |
290 | | Agent -|----(B)-- User authenticates -->| Server |
291 | | | | |
292 | | |<---(C)--- Redirection URI ----<| |
293 | | | with Access Token +---------------+
294 | | | in Fragment
295 | | | +---------------+
296 | | |----(D)--- Redirection URI ---->| Web-Hosted |
297 | | | without Fragment | Client |
298 | | | | Resource |
299 | | (F) |<---(E)------- Script ---------<| |
300 | | | +---------------+
301 | +-|--------+
302 | | |
303 | (A) (G) Access Token
304 | | |
305 | ^ v
306 | +---------+
307 | | |
308 | | Client |
309 | | |
310 | +---------+
311 |
312 | Note: The lines illustrating steps (A) and (B) are broken into two parts
313 | as they pass through the user-agent.
314 |
315 | Figure 4: Implicit Grant Flow
316 |
317 | The following is how the Grant Type works in this application :
318 |
319 | * Generate random `state` variable :
320 |
321 | curl http://localhost:8081/resource-server/api/state/new
322 | This state variable will be save as session attribute in server, we will use it for verification in next step.
323 |
324 | * Generate token with `state` variable :
325 |
326 | curl http://localhost:8080/authorization-server/oauth/authorize?client_id=jsclient&response_type=token&scope=write&state=STATE
327 |
328 | * `auth-server` will redirected to login page.
329 | * Login with username=`admin` and password=`passw0rd`
330 | * After success login, `auth-server` will redirected to URL `http://localhost:8081/resource-server/api/state/verify` with additinal hash token :
331 |
332 | `http://localhost:8081/resource-server/api/state/verify#access_token=fdd3ed9d-f378-406b-9d23-13b36aad5128&token_type=bearer&state=d6b63cdb-bbf0-4232-b3a2-5855c1b12b1d&expires_in=86399`
333 |
334 | * Access protected resource :
335 |
336 | curl http://localhost:8081/resource-server/api/admin?access_token=fdd3ed9d-f378-406b-9d23-13b36aad5128
337 |
338 | And You can access it with header parameter as `Authorization` :
339 |
340 | curl -H "Authorization: Bearer 667aadee-883c-439f-9f18-50ef77e3fad6" http://localhost:8081/resource-server/api/admin
341 |
342 |
343 | ### References
344 | * [Spring Security Guides] (http://docs.spring.io/spring-security/site/docs/current/guides/html5/)
345 | * [Spring OAuth2 Developer Guide] (http://projects.spring.io/spring-security-oauth/docs/oauth2.html)
346 | * [IETF] (https://tools.ietf.org/html/rfc6749)
347 | * [Endy Muhardin Github Page] (https://github.com/endymuhardin/belajar-springoauth2)
--------------------------------------------------------------------------------
/authorization-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.arip.springmvc.oauth2
8 | authorization-server
9 | 1.0-SNAPSHOT
10 | war
11 |
12 | Spring MVC Security OAuth2 Example (Authorization Server)
13 |
14 |
15 | UTF-8
16 | 2.0.8.RELEASE
17 | 3.1.0
18 | 2.1
19 | 2.2
20 |
21 |
22 |
23 |
24 | org.springframework.security.oauth
25 | spring-security-oauth2
26 | ${spring.security.oauth2.version}
27 |
28 |
29 |
30 | javax.servlet
31 | javax.servlet-api
32 | ${servlet.version}
33 | provided
34 |
35 |
36 |
37 | javax.servlet.jsp
38 | jsp-api
39 | ${jsp.version}
40 | provided
41 |
42 |
43 |
44 |
45 |
46 |
47 | org.apache.tomcat.maven
48 | tomcat7-maven-plugin
49 | ${tomcat.version}
50 |
51 | 8080
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/org/arip/springmvc/oauth2/config/AppConfig.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc;
6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
7 |
8 | /**
9 | * Created by Arip Hidayat on 12/8/2015.
10 | */
11 | @Configuration
12 | @EnableWebMvc
13 | public class AppConfig extends WebMvcConfigurerAdapter {
14 |
15 | @Override
16 | public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
17 | configurer.enable();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/org/arip/springmvc/oauth2/config/AuthorizationServerConfig.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Qualifier;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.security.authentication.AuthenticationManager;
7 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
8 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
9 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
10 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
11 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
12 | import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
13 |
14 | /**
15 | * Created by Arip Hidayat on 12/03/2016.
16 | */
17 | @Configuration
18 | @EnableAuthorizationServer
19 | public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
20 |
21 | public static final String RESOURCE_ID = "arip";
22 |
23 | @Autowired
24 | @Qualifier("authenticationManagerBean")
25 | private AuthenticationManager authenticationManager;
26 |
27 | @Override
28 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
29 | endpoints.tokenStore(new InMemoryTokenStore()).authenticationManager(authenticationManager);
30 | }
31 |
32 | @Override
33 | public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
34 | oauthServer.checkTokenAccess("hasRole('CLIENT')");
35 | }
36 |
37 | @Override
38 | public void configure(ClientDetailsServiceConfigurer client) throws Exception {
39 | client.inMemory()
40 | .withClient("clientapp")
41 | .secret("123456")
42 | .authorizedGrantTypes("password")
43 | .scopes("read", "write")
44 | .resourceIds(RESOURCE_ID)
45 | .and()
46 | .withClient("clientcred")
47 | .secret("123456")
48 | .authorizedGrantTypes("client_credentials")
49 | .scopes("trust")
50 | .resourceIds(RESOURCE_ID)
51 | .and()
52 | .withClient("clientauthcode")
53 | .secret("123456")
54 | .authorizedGrantTypes("authorization_code", "refresh_token")
55 | .scopes("read", "write")
56 | .resourceIds(RESOURCE_ID)
57 | .and()
58 | .withClient("jsclient")
59 | .secret("123456")
60 | .authorizedGrantTypes("implicit")
61 | .scopes("read", "write")
62 | .resourceIds(RESOURCE_ID)
63 | .authorities("CLIENT")
64 | .redirectUris("http://localhost:8081/resource-server/api/state/verify")
65 | .accessTokenValiditySeconds(3600)
66 | .autoApprove(true);
67 | }
68 | }
--------------------------------------------------------------------------------
/authorization-server/src/main/java/org/arip/springmvc/oauth2/config/SecurityConfig.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.security.authentication.AuthenticationManager;
7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
11 |
12 | /**
13 | * Created by Arip Hidayat on 12/10/2015.
14 | */
15 | @Configuration
16 | @EnableWebSecurity(debug = true)
17 | public class SecurityConfig extends WebSecurityConfigurerAdapter {
18 |
19 | @Autowired
20 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
21 | auth.inMemoryAuthentication().withUser("admin").password("passw0rd").roles("ADMIN");
22 | auth.inMemoryAuthentication().withUser("staff").password("passw0rd").roles("STAFF");
23 | }
24 |
25 | @Bean
26 | @Override
27 | public AuthenticationManager authenticationManagerBean() throws Exception {
28 | return super.authenticationManagerBean();
29 | }
30 |
31 | @Override
32 | public void configure(HttpSecurity http) throws Exception {
33 | http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
34 |
35 | http.authorizeRequests().antMatchers("/login.jsp").permitAll();
36 | http.authorizeRequests().and().formLogin().loginPage("/login.jsp").loginProcessingUrl("/j_spring_security_check")
37 | .usernameParameter("j_username").passwordParameter("j_password");
38 | http.authorizeRequests().and().logout().logoutUrl("/j_spring_security_logout");
39 | }
40 | }
--------------------------------------------------------------------------------
/authorization-server/src/main/java/org/arip/springmvc/oauth2/config/core/DispatcherServletInitializer.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config.core;
2 |
3 | import org.springframework.web.WebApplicationInitializer;
4 | import org.springframework.web.context.ContextLoaderListener;
5 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
6 | import org.springframework.web.servlet.DispatcherServlet;
7 |
8 | import javax.servlet.ServletContext;
9 | import javax.servlet.ServletException;
10 | import javax.servlet.ServletRegistration;
11 |
12 | /**
13 | * Created by Arip Hidayat on 12/8/2015.
14 | */
15 | public class DispatcherServletInitializer implements WebApplicationInitializer {
16 |
17 | public void onStartup(ServletContext servletContext) throws ServletException {
18 | AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
19 | context.scan("org.arip.springmvc.oauth2");
20 |
21 | servletContext.addListener(new ContextLoaderListener(context));
22 |
23 | ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
24 | dispatcher.setLoadOnStartup(1);
25 | dispatcher.addMapping("/");
26 |
27 | }
28 | }
--------------------------------------------------------------------------------
/authorization-server/src/main/java/org/arip/springmvc/oauth2/config/core/SecurityInitializer.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config.core;
2 |
3 | import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
4 |
5 | /**
6 | * Created by Arip Hidayat on 12/10/2015.
7 | */
8 | public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
9 | // do nothing
10 | }
11 |
--------------------------------------------------------------------------------
/authorization-server/src/main/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Index Page
5 |
6 |
7 | Hallo API
8 | Admin API
9 | Staff API
10 |
11 |
--------------------------------------------------------------------------------
/authorization-server/src/main/webapp/login.jsp:
--------------------------------------------------------------------------------
1 | <%@page contentType="text/html" pageEncoding="UTF-8"%>
2 |
3 |
4 |
5 |
6 |
7 | <% if (request.getParameter("error") != null) { %>
8 |
${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}
9 | <% } %>
10 |
11 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/implicit-client/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "src/main/js/vendor"
3 | }
--------------------------------------------------------------------------------
/implicit-client/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | vendor
--------------------------------------------------------------------------------
/implicit-client/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "implicit-client",
3 | "version": "1.0.0",
4 | "description": "Spring MVC Security OAuth2 Example (Client of Implicit Grant Type)",
5 | "authors": [
6 | "Arip"
7 | ],
8 | "license": "MIT",
9 | "dependencies": {
10 | "angular": "^1.5.0"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/implicit-client/gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | grunt.initConfig({
4 |
5 | wiredep: {
6 | task: {
7 | src: 'src/main/**/*.html'
8 | }
9 | },
10 |
11 | connect: {
12 | sever: {
13 | options: {
14 | hostname: 'localhost',
15 | port: 3000,
16 | base: 'src/main/',
17 | livereload: true
18 | }
19 | }
20 | },
21 |
22 | watch: {
23 | options: {
24 | spawn: false,
25 | livereload: true
26 | },
27 | scripts: {
28 | files: ['src/main/**/*.html',
29 | 'src/main/**/*.js',
30 | 'src/main/**/*.css']
31 | }
32 | }
33 |
34 |
35 | }); //initConfig
36 |
37 | grunt.loadNpmTasks('grunt-contrib-watch');
38 | grunt.loadNpmTasks('grunt-contrib-connect');
39 | grunt.loadNpmTasks('grunt-wiredep');
40 |
41 | grunt.registerTask('default', ['wiredep', 'connect', 'watch']);
42 |
43 | }; //wrapper function
--------------------------------------------------------------------------------
/implicit-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "implicit-client",
3 | "version": "1.0.0",
4 | "description": "Spring MVC Security OAuth2 Example (Client of Implicit Grant Type)",
5 | "author": "Arip Hidayat",
6 | "license": "MIT",
7 | "devDependencies": {
8 | "grunt": "^0.4.5",
9 | "grunt-contrib-connect": "^0.10.1",
10 | "grunt-contrib-watch": "^0.6.1",
11 | "grunt-wiredep": "^2.0.0"
12 | }
13 | }
--------------------------------------------------------------------------------
/implicit-client/src/main/css/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ariphidayat/springmvc-oauth2-example/c145960954915b963c218d635c0df5ca605105ee/implicit-client/src/main/css/style.css
--------------------------------------------------------------------------------
/implicit-client/src/main/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Implicit Client
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Spring MVC Security OAuth2 Example (Client of Implicit Grant Type)
16 |
19 |
20 |
21 | Access Token : {{accessToken}}
22 |
User : {{currentUser}}
23 |
24 | {{adminOutput}}
25 | {{staffOutput}}
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/implicit-client/src/main/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var app = angular.module('aplikasiOauthClient', []);
4 |
5 | app.config(function($locationProvider) {
6 | $locationProvider.html5Mode(true);
7 | });
8 |
9 | app.controller('NavCtrl', function($scope, $window, $location, $http) {
10 | $scope.authUrl = 'http://localhost:8080/authorization-server/oauth/authorize?client_id=jsclient&response_type=token&scope=write';
11 |
12 | $scope.token;
13 |
14 | $scope.login = function() {
15 | $window.location.href = $scope.authUrl;
16 | };
17 |
18 | $scope.getTokenFromUrl = function() {
19 | var token;
20 | var hashParams = $location.hash();
21 | if (!hashParams) {
22 | console.log("No token in URL");
23 | return;
24 | }
25 | console.log(hashParams);
26 | var eachParam = hashParams.split('&');
27 | for (var i = 0; i < eachParam.length; i++) {
28 | var param = eachParam[i].split('=');
29 | if ('access_token' === param[0]) {
30 | token = param[1];
31 | }
32 | }
33 | console.log("Access Token : " + token);
34 | if (token) {
35 | $window.sessionStorage.setItem('token', token);
36 | }
37 | $location.hash('');
38 | };
39 |
40 | $scope.checkLogin = function() {
41 | if ($window.sessionStorage.getItem('token')) {
42 | $scope.token = $window.sessionStorage.getItem('token');
43 | return;
44 | }
45 | $scope.getTokenFromUrl();
46 | if ($window.sessionStorage.getItem('token')) {
47 | $scope.token = $window.sessionStorage.getItem('token');
48 | return;
49 | }
50 |
51 | $scope.login();
52 | };
53 |
54 | $scope.checkLogin();
55 | });
56 |
57 | app.controller('OauthCtrl', function($scope, $http, $window) {
58 | $scope.currentUser;
59 | $scope.accessToken = $window.sessionStorage.getItem('token');
60 |
61 | $scope.adminApi = function() {
62 | if (!$scope.accessToken) {
63 | console.log("have no token");
64 | return;
65 | }
66 |
67 | //call Admin API
68 | $http.get('http://localhost:8081/resource-server/api/admin?access_token=' + $scope.accessToken)
69 | .success(function(data) {
70 | $scope.adminOutput = data;
71 | $scope.currentUser = data.user;
72 | }).error(function(data, status) {
73 | console.log("Error : " + status + " - " + data);
74 | $scope.adminOutput = data;
75 | });
76 | };
77 |
78 | $scope.staffApi = function() {
79 | if (!$scope.accessToken) {
80 | console.log("Have no token");
81 | return;
82 | }
83 |
84 | //call Staff API
85 | $http.get('http://localhost:8081/resource-server/api/staff?access_token=' + $scope.accessToken)
86 | .success(function(data) {
87 | $scope.staffOutput = data;
88 | $scope.currentUser = data.user;
89 | }).error(function(data, status) {
90 | console.log("Error : " + status + " - " + data);
91 | $scope.staffOutput = data;
92 | });
93 | };
94 | });
95 |
--------------------------------------------------------------------------------
/resource-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.arip.springmvc.oauth2
8 | resource-server
9 | 1.0-SNAPSHOT
10 | war
11 |
12 | Spring MVC Security OAuth2 Example (Resource Server)
13 |
14 |
15 | UTF-8
16 | 2.0.8.RELEASE
17 | 3.1.0
18 | 2.1
19 | 2.2
20 |
21 |
22 |
23 |
24 | org.springframework.security.oauth
25 | spring-security-oauth2
26 | ${spring.security.oauth2.version}
27 |
28 |
29 |
30 | javax.servlet
31 | javax.servlet-api
32 | ${servlet.version}
33 | provided
34 |
35 |
36 |
37 | javax.servlet.jsp
38 | jsp-api
39 | ${jsp.version}
40 | provided
41 |
42 |
43 |
44 |
45 |
46 |
47 | org.apache.tomcat.maven
48 | tomcat7-maven-plugin
49 | ${tomcat.version}
50 |
51 | 8081
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/org/arip/springmvc/oauth2/config/AppConfig.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config;
2 |
3 | import org.arip.springmvc.oauth2.filter.CORSFilter;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.ComponentScan;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.context.annotation.PropertySource;
9 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
10 | import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
11 | import org.springframework.web.servlet.config.annotation.EnableWebMvc;
12 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
14 |
15 | /**
16 | * Created by Arip Hidayat on 12/8/2015.
17 | */
18 | @Configuration
19 | @EnableWebMvc
20 | @ComponentScan
21 | @PropertySource("classpath:clients.properties")
22 | public class AppConfig extends WebMvcConfigurerAdapter {
23 |
24 | @Value("${allowedHosts}")
25 | private String allowedHosts;
26 |
27 | @Override
28 | public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
29 | configurer.enable();
30 | }
31 |
32 | @Bean
33 | public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
34 | return new PropertySourcesPlaceholderConfigurer();
35 | }
36 |
37 | @Override
38 | public void addInterceptors(InterceptorRegistry registry) {
39 | System.out.println("Allowed Host : "+allowedHosts);
40 | registry.addInterceptor(new CORSFilter(allowedHosts));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/org/arip/springmvc/oauth2/config/ResourceServerConfig.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
5 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
6 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
7 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
8 | import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
9 |
10 | /**
11 | * Created by Arip Hidayat on 12/03/2016.
12 | */
13 | @Configuration
14 | @EnableResourceServer
15 | public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
16 |
17 | public static final String RESOURCE_ID = "arip";
18 |
19 | @Override
20 | public void configure(HttpSecurity http) throws Exception {
21 | http.authorizeRequests().antMatchers("/api/admin").hasRole("ADMIN");
22 | http.authorizeRequests().antMatchers("/api/staff").hasRole("STAFF");
23 | http.authorizeRequests().antMatchers("/api/client").access("#oauth2.hasScope('trust')");
24 | }
25 |
26 | @Override
27 | public void configure(ResourceServerSecurityConfigurer resources) {
28 | RemoteTokenServices tokenService = new RemoteTokenServices();
29 | tokenService.setClientId("jsclient");
30 | tokenService.setClientSecret("123456");
31 | tokenService.setCheckTokenEndpointUrl("http://localhost:8080/authorization-server/oauth/check_token");
32 |
33 | resources.resourceId(RESOURCE_ID).tokenServices(tokenService);
34 | }
35 | }
--------------------------------------------------------------------------------
/resource-server/src/main/java/org/arip/springmvc/oauth2/config/SecurityConfig.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
6 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
7 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
8 |
9 | /**
10 | * Created by Arip Hidayat on 12/10/2015.
11 | */
12 | @Configuration
13 | @EnableWebSecurity(debug = true)
14 | public class SecurityConfig extends WebSecurityConfigurerAdapter {
15 |
16 | @Autowired
17 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
18 | auth.inMemoryAuthentication();
19 | }
20 | }
--------------------------------------------------------------------------------
/resource-server/src/main/java/org/arip/springmvc/oauth2/config/core/DispatcherServletInitializer.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config.core;
2 |
3 | import org.springframework.web.WebApplicationInitializer;
4 | import org.springframework.web.context.ContextLoaderListener;
5 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
6 | import org.springframework.web.servlet.DispatcherServlet;
7 |
8 | import javax.servlet.ServletContext;
9 | import javax.servlet.ServletException;
10 | import javax.servlet.ServletRegistration;
11 |
12 | /**
13 | * Created by Arip Hidayat on 12/8/2015.
14 | */
15 | public class DispatcherServletInitializer implements WebApplicationInitializer {
16 |
17 | public void onStartup(ServletContext servletContext) throws ServletException {
18 | AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
19 | context.scan("org.arip.springmvc.oauth2");
20 |
21 | servletContext.addListener(new ContextLoaderListener(context));
22 |
23 | ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
24 | dispatcher.setLoadOnStartup(1);
25 | dispatcher.addMapping("/");
26 |
27 | }
28 | }
--------------------------------------------------------------------------------
/resource-server/src/main/java/org/arip/springmvc/oauth2/config/core/SecurityInitializer.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.config.core;
2 |
3 | import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
4 |
5 | /**
6 | * Created by Arip Hidayat on 12/10/2015.
7 | */
8 | public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
9 | // do nothing
10 | }
11 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/org/arip/springmvc/oauth2/controller/ApiController.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.controller;
2 |
3 | import org.springframework.web.bind.annotation.RequestMapping;
4 | import org.springframework.web.bind.annotation.RestController;
5 |
6 | import javax.servlet.http.HttpSession;
7 | import java.security.Principal;
8 | import java.util.HashMap;
9 | import java.util.Map;
10 | import java.util.UUID;
11 |
12 | /**
13 | * Created by Arip Hidayat on 12/8/2015.
14 | */
15 | @RestController
16 | @RequestMapping("/api")
17 | public class ApiController {
18 |
19 | @RequestMapping("/hallo")
20 | public Map hallo() {
21 | Map result = new HashMap();
22 | result.put("success", Boolean.TRUE);
23 | result.put("page", "hallo");
24 |
25 | return result;
26 | }
27 |
28 | @RequestMapping("/admin")
29 | public Map admin(Principal user) {
30 | Map result = new HashMap();
31 | result.put("success", Boolean.TRUE);
32 | result.put("page", "admin");
33 | result.put("user", user.getName());
34 |
35 | return result;
36 | }
37 |
38 | @RequestMapping("/staff")
39 | public Map staff(Principal user) {
40 | Map result = new HashMap();
41 | result.put("success", Boolean.TRUE);
42 | result.put("page", "staff");
43 | result.put("user", user.getName());
44 |
45 | return result;
46 | }
47 |
48 | @RequestMapping("/client")
49 | public Map client(Principal user) {
50 | Map result = new HashMap();
51 | result.put("success", Boolean.TRUE);
52 | result.put("page", "client");
53 | result.put("user", user.getName());
54 |
55 | return result;
56 | }
57 |
58 | @RequestMapping("/state/new")
59 | public Map newState(HttpSession session) {
60 | Map result = new HashMap();
61 | result.put("success", Boolean.TRUE);
62 |
63 | String state = UUID.randomUUID().toString();
64 | result.put("state", state);
65 | session.setAttribute("state", state);
66 |
67 | return result;
68 | }
69 |
70 | @RequestMapping("/state/verify")
71 | public Map verify(HttpSession session) {
72 | Map result = new HashMap();
73 | result.put("success", Boolean.TRUE);
74 |
75 | String state = (String) session.getAttribute("state");
76 | result.put("state", state);
77 | session.removeAttribute("state");
78 |
79 | return result;
80 | }
81 | }
--------------------------------------------------------------------------------
/resource-server/src/main/java/org/arip/springmvc/oauth2/filter/CORSFilter.java:
--------------------------------------------------------------------------------
1 | package org.arip.springmvc.oauth2.filter;
2 |
3 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
4 |
5 | import javax.servlet.http.HttpServletRequest;
6 | import javax.servlet.http.HttpServletResponse;
7 | import java.util.Arrays;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 |
11 | /**
12 | * Created by Arip Hidayat on 13/03/2016.
13 | */
14 | public class CORSFilter extends HandlerInterceptorAdapter {
15 |
16 | private final String allowedHosts;
17 |
18 | public CORSFilter(String allowedHosts) {
19 | this.allowedHosts = allowedHosts;
20 | }
21 |
22 | @Override
23 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
24 | Set allowedOrigins = new HashSet(Arrays.asList(allowedHosts.split(",")));
25 | String origin = request.getHeader("Origin");
26 | if (allowedOrigins.contains(origin)) {
27 | System.out.println("Origin "+origin+" exist in clients.properties");
28 | response.addHeader("Access-Control-Allow-Origin", origin);
29 | if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
30 | response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
31 | response.addHeader("Access-Control-Allow-Headers", "Content-Type");
32 | response.addHeader("Access-Control-Max-Age", "1"); // 30 min
33 | }
34 | return true;
35 | } else {
36 | System.out.println("Origin " + origin + " not exist in clients.properties");
37 | return true;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/resource-server/src/main/resources/clients.properties:
--------------------------------------------------------------------------------
1 | allowedHosts=http://localhost:3000
--------------------------------------------------------------------------------
/resource-server/src/main/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Index Page
5 |
6 |
7 | Hallo API
8 | Admin API
9 | Staff API
10 |
11 |
--------------------------------------------------------------------------------