├── .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 |
12 | 13 | 14 | 17 | 18 | 21 | 22 | 23 |
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 |
17 | Logout 18 |
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 | --------------------------------------------------------------------------------