├── .gitignore ├── README.md ├── authorization-server ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── exteso │ │ └── blog │ │ └── oauth2 │ │ └── stepbystep │ │ ├── AuthenticationServer.java │ │ └── configuration │ │ └── OAuth2Config.java │ └── resources │ ├── application.yml │ └── jwt.jks ├── pom.xml ├── resource-server ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── exteso │ │ └── blog │ │ └── oauth2 │ │ └── stepbystep │ │ └── ResourceServer.java │ └── resources │ └── application.yml └── webapp-server ├── pom.xml └── src └── main ├── java └── com │ └── exteso │ └── blog.oauth2.stepbystep │ ├── App.java │ └── configuration │ └── WebSecurityConfig.java └── resources ├── application.yml └── static └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | .DS_Store 11 | .idea 12 | *.iml 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repo has been started by [@syjer](https://github.com/syjer) as [oauthtest](https://github.com/exteso/oauthtest), to cleanup history and make it cleaner for a blog post I created this new Repo 2 | 3 | This example is based on the following resources: 4 | 5 | - http://stytex.de/blog/2016/02/01/spring-cloud-security-with-oauth2/ 6 | - https://github.com/spring-cloud-samples/authserver/blob/master/src/main/java/demo/AuthserverApplication.java 7 | - http://projects.spring.io/spring-security-oauth/docs/oauth2.html 8 | - https://github.com/spring-guides/tut-spring-boot-oauth2/ 9 | - https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/oauth2 10 | - https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/oauth2-vanilla 11 | - https://spring.io/blog/2015/11/30/migrating-oauth2-apps-from-spring-boot-1-2-to-1-3 12 | 13 | 14 | How to test: 15 | 16 | 1. $ `cd authorization-server;mvn spring-boot:run` 17 | 2. $ `cd resource-server;mvn spring-boot:run` 18 | 19 | 3. Obtain token with: $ `curl service-account-1:service-account-1-secret@localhost:8080/auth/oauth/token -d grant_type=client_credentials` and save it in TOKEN=....... 20 | 4. Access the resource with: $ `curl -H "Authorization: Bearer $TOKEN" -v localhost:9090` 21 | 5. Update the resource with: $ `curl -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -X POST -d "Bonjour" -v localhost:9090` 22 | 23 | 6. $ `cd webapp-server;mvn spring-boot:run` 24 | 7. go to localhost:9999 and use the UI :). 25 | 26 | You can load the message from backend server, then submit a new one and try to reload. 27 | All calls are using JWT, AS is called only the first time, RS checks the client has correct scopes. 28 | If token expires it automatically goes to AS to get a new one. 29 | 30 | For generating your own key (as written in the stytex.de blog): 31 | 32 | ` 33 | keytool -genkeypair -alias jwt -keyalg RSA -dname "CN=jwt, L=Lugano, S=Lugano, C=CH" -keypass mySecretKey -keystore jwt.jks -storepass mySecretKey 34 | ` 35 | 36 | copy jwt.jks in authorization-server/src/main/resources/jwk.jks 37 | 38 | Notes: 39 | 40 | - Resource server fetch the pubkey of the authentication server, so in production it must be over a secure channel :) 41 | - If the authentication server is down, and a resource server is launched, the fetch of the public key will fail (but a log message will be written), see https://github.com/spring-projects/spring-security-oauth/issues/734 issue 42 | -------------------------------------------------------------------------------- /authorization-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 1.4.1.RELEASE 11 | 12 | 13 | 14 | 15 | com.exteso.blog.oauth2.step-by-step 16 | authorization-server 17 | 1.0-SNAPSHOT 18 | authorization-server 19 | 20 | UTF-8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-security 31 | 32 | 33 | org.springframework.security.oauth 34 | spring-security-oauth2 35 | 36 | 37 | org.springframework.security 38 | spring-security-jwt 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/exteso/blog/oauth2/stepbystep/AuthenticationServer.java: -------------------------------------------------------------------------------- 1 | package com.exteso.blog.oauth2.stepbystep; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.security.Principal; 12 | 13 | @SpringBootApplication 14 | @RestController 15 | @EnableResourceServer 16 | public class AuthenticationServer { 17 | public static void main(String[] args) { 18 | SpringApplication.run(AuthenticationServer.class, args); 19 | } 20 | 21 | private static final Log logger = LogFactory.getLog(AuthenticationServer.class); 22 | 23 | @RequestMapping("/user") 24 | public Principal user(Principal user) { 25 | logger.info("AS /user has been called"); 26 | logger.debug("user info: " + user.toString()); 27 | return user; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /authorization-server/src/main/java/com/exteso/blog/oauth2/stepbystep/configuration/OAuth2Config.java: -------------------------------------------------------------------------------- 1 | package com.exteso.blog.oauth2.stepbystep.configuration; 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.core.env.Environment; 7 | import org.springframework.core.io.ClassPathResource; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 10 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 11 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 12 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 13 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 14 | import org.springframework.security.oauth2.provider.token.TokenStore; 15 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 16 | import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 17 | import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; 18 | 19 | @Configuration 20 | @EnableAuthorizationServer 21 | public class OAuth2Config extends AuthorizationServerConfigurerAdapter { 22 | 23 | @Autowired 24 | private AuthenticationManager authenticationManager; 25 | 26 | @Autowired 27 | private Environment environment; 28 | 29 | @Override 30 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 31 | endpoints.tokenStore(tokenStore()) 32 | .tokenEnhancer(jwtTokenEnhancer()) 33 | .authenticationManager(authenticationManager); 34 | } 35 | 36 | @Override 37 | public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 38 | security.tokenKeyAccess("permitAll()") 39 | .checkTokenAccess("isAuthenticated()"); 40 | } 41 | 42 | @Bean 43 | public TokenStore tokenStore() { 44 | return new JwtTokenStore(jwtTokenEnhancer()); 45 | } 46 | 47 | @Bean 48 | protected JwtAccessTokenConverter jwtTokenEnhancer() { 49 | String pwd = environment.getProperty("keystore.password"); 50 | KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory( 51 | new ClassPathResource("jwt.jks"), 52 | pwd.toCharArray()); 53 | JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); 54 | converter.setKeyPair(keyStoreKeyFactory.getKeyPair("jwt")); 55 | return converter; 56 | } 57 | 58 | @Override 59 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 60 | clients.inMemory() 61 | .withClient("service-account-1") 62 | .secret("service-account-1-secret") 63 | .authorizedGrantTypes("client_credentials") 64 | .scopes("resource-server-read", "resource-server-write") 65 | .accessTokenValiditySeconds(60); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /authorization-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server.contextPath: /auth 2 | 3 | logging: 4 | level: 5 | org.springframework.security: DEBUG 6 | server: 7 | port: 8080 8 | keystore: 9 | password: mySecretKey -------------------------------------------------------------------------------- /authorization-server/src/main/resources/jwt.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exteso/oauth2-step-by-step/d97360deccbdc1352c2706c6de8899937188098f/authorization-server/src/main/resources/jwt.jks -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.exteso.blog.oauth2.step-by-step 6 | step-by-step 7 | 1.0-SNAPSHOT 8 | pom 9 | 10 | OAuth2: step-by-step introduction 11 | 12 | 13 | 14 | UTF-8 15 | 1.8 16 | 17 | 18 | 19 | authorization-server 20 | resource-server 21 | webapp-server 22 | 23 | 24 | -------------------------------------------------------------------------------- /resource-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 1.4.1.RELEASE 11 | 12 | 13 | 14 | com.exteso.blog.oauth2.step-by-step 15 | resource-server 16 | 1.0-SNAPSHOT 17 | resource-server 18 | 19 | 20 | UTF-8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.security.oauth 30 | spring-security-oauth2 31 | 32 | 33 | org.springframework.security 34 | spring-security-jwt 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-maven-plugin 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /resource-server/src/main/java/com/exteso/blog/oauth2/stepbystep/ResourceServer.java: -------------------------------------------------------------------------------- 1 | package com.exteso.blog.oauth2.stepbystep; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.security.access.prepost.PreAuthorize; 6 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 7 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import java.security.Principal; 14 | import java.util.Collections; 15 | import java.util.Map; 16 | 17 | @SpringBootApplication 18 | @RestController 19 | @EnableGlobalMethodSecurity(prePostEnabled = true) 20 | @EnableResourceServer 21 | public class ResourceServer { 22 | 23 | public static void main(String[] args) { 24 | SpringApplication.run(ResourceServer.class, args); 25 | } 26 | 27 | private String message = "Hello world!"; 28 | 29 | @PreAuthorize("#oauth2.hasScope('resource-server-read')") 30 | @RequestMapping(value = "/", method = RequestMethod.GET) 31 | public Map home() { 32 | return Collections.singletonMap("message", message); 33 | } 34 | 35 | @PreAuthorize("#oauth2.hasScope('resource-server-write')") 36 | @RequestMapping(value = "/", method = RequestMethod.POST) 37 | public void updateMessage(@RequestBody String message) { 38 | this.message = message; 39 | } 40 | 41 | @PreAuthorize("#oauth2.hasScope('resource-server-read')") 42 | @RequestMapping(value = "/user", method = RequestMethod.GET) 43 | public Map user(Principal user) { 44 | return Collections.singletonMap("message", "user is: " + user.toString()); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /resource-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | org.springframework.security: DEBUG 4 | 5 | security: 6 | oauth2: 7 | resource: 8 | jwt: 9 | keyUri: http://localhost:8080/auth/oauth/token_key 10 | 11 | server: 12 | port: 9090 -------------------------------------------------------------------------------- /webapp-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | 9 | org.springframework.boot 10 | spring-boot-starter-parent 11 | 1.4.1.RELEASE 12 | 13 | 14 | 15 | 16 | com.exteso.com.exteso.blog.oauth2.step-by-step 17 | webapp-server 18 | 1.0-SNAPSHOT 19 | webapp-server 20 | 21 | 22 | 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | org.springframework.security.oauth 35 | spring-security-oauth2 36 | 37 | 38 | org.springframework.security 39 | spring-security-jwt 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-maven-plugin 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /webapp-server/src/main/java/com/exteso/blog.oauth2.stepbystep/App.java: -------------------------------------------------------------------------------- 1 | package com.exteso.blog.oauth2.stepbystep; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; 10 | import org.springframework.security.oauth2.client.OAuth2RestTemplate; 11 | import org.springframework.security.oauth2.client.token.AccessTokenRequest; 12 | import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; 13 | import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | import java.util.Map; 20 | 21 | @SpringBootApplication 22 | @RestController 23 | public class App { 24 | 25 | @Autowired 26 | private OAuth2RestTemplate resourceServerProxy; 27 | 28 | public static void main(String[] args) { 29 | SpringApplication.run(App.class, args); 30 | } 31 | 32 | @RequestMapping(value = "/api/message", method = RequestMethod.GET) 33 | public Map getMessage() { 34 | return resourceServerProxy.getForObject("http://localhost:9090", Map.class); 35 | } 36 | 37 | @RequestMapping(value = "/api/message", method = RequestMethod.POST) 38 | public void saveMessage(@RequestBody String newMessage) { 39 | resourceServerProxy.postForLocation("http://localhost:9090", newMessage); 40 | } 41 | 42 | @Configuration 43 | public static class OauthClientConfiguration { 44 | 45 | @Bean 46 | @ConfigurationProperties("resourceServerClient") 47 | public ClientCredentialsResourceDetails getClientCredentialsResourceDetails() { 48 | return new ClientCredentialsResourceDetails(); 49 | } 50 | 51 | @Bean 52 | public OAuth2RestTemplate restTemplate() { 53 | AccessTokenRequest atr = new DefaultAccessTokenRequest(); 54 | return new OAuth2RestTemplate(getClientCredentialsResourceDetails(), new DefaultOAuth2ClientContext(atr)); 55 | } 56 | 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /webapp-server/src/main/java/com/exteso/blog.oauth2.stepbystep/configuration/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.exteso.blog.oauth2.stepbystep.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 6 | 7 | @Configuration 8 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 9 | 10 | @Override 11 | protected void configure(HttpSecurity http) throws Exception { 12 | http.csrf().disable(); 13 | http.authorizeRequests().antMatchers("/**").permitAll(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /webapp-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9999 3 | logging: 4 | level: 5 | org.springframework.security: DEBUG 6 | 7 | spring: 8 | aop: 9 | proxy-target-class: true 10 | 11 | security: 12 | oauth2: 13 | resource: 14 | jwt: 15 | keyUri: http://localhost:8080/auth/oauth/token_key 16 | 17 | 18 | resourceServerClient: 19 | accessTokenUri: http://localhost:8080/auth/oauth/token 20 | clientId: service-account-1 21 | clientSecret: service-account-1-secret -------------------------------------------------------------------------------- /webapp-server/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My application 5 | 6 | 7 |

Unsecured page

8 | 9 | 10 |
Message is:
11 | 12 | 13 |
14 | 15 | 16 | 29 | 30 | 31 | --------------------------------------------------------------------------------