├── .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 |
--------------------------------------------------------------------------------