I this post, using spring boot, I'll show a basic Oauth2 flow with :
- Authorization server
- Client app which logs in to Authorization server using username and password, takes login token as a response of successful login and calls resource server with received token.
- Resource server(which have protected resource) handles requests, grabs token from the request, validates tokens on Authorization server, returns requested data.
So, I have to create 3 separate spring boot applications(Authorization server, Client app, Resource server), run them, and make sure all flow works.
In properties, we are defining clientId, clientSecret (password) which should be used for authorization. Also we should define grant-types, and if we need them - scopes (in this example scope will not be used).
application.properties:
In service config file I' defining users(huan and joe) of my application with passwords. Of course, in real life they will not be defined in a code, but should be stored in DB.
ServiceConfig:
In server runner application we have to define endpoint for getting user details - we will use it later.
Server runner application:
Resource server is a spring boot application which has some protected resource(endpoint "/mydata"), which is not accessible without authorization. Client should provide authorization token to call this endpoint. In properties file we should define endpoint from authorization server mentioned above.
application.properties
Service config:
In main class we're just defining endpoint and class with some very important data.
Application runner:
Client application is a most interesting thing here. First of all we have to define in properties detail for authorization.
application.yml
In main application runner we are about to call our protected endpoint: http://localhost:9001/services/mydata
But for this call we should be authorized first. I'm using credentials of user "joe" for this.
Also I'm printing authorization token, to make sure we have it.
And finally I'm calling this endpoint.
Application runner:
After that, we're free to go: let's start our client.
For me it produced next output:
starting
token:2b9560c9-355d-4134-a63b-e10f05b1b9b4
Result:[{"myId":1,"myValue":"one"},{"myId":2,"myValue":"two"},{"myId":3,"myValue":"three"}]
It looks simple, but under the hood client called authorization server, to get token, called resource service with token saved in session. Resource service called authorization server again to validate the token and after that - returned result back to client.
- Authorization server
- Client app which logs in to Authorization server using username and password, takes login token as a response of successful login and calls resource server with received token.
- Resource server(which have protected resource) handles requests, grabs token from the request, validates tokens on Authorization server, returns requested data.
So, I have to create 3 separate spring boot applications(Authorization server, Client app, Resource server), run them, and make sure all flow works.
0. Dependencies
For simplicity I'm using the same dependencies for all 3 applications:dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile('org.springframework.cloud:spring-cloud-starter-oauth2') compile('org.springframework.cloud:spring-cloud-starter-security') testCompile('org.springframework.boot:spring-boot-starter-test') }
1. Authorization server
Authorization server it's a spring boot application which will be used to authorize user by credentials sent by client application. As a response it should send a token back to client.In properties, we are defining clientId, clientSecret (password) which should be used for authorization. Also we should define grant-types, and if we need them - scopes (in this example scope will not be used).
application.properties:
server.port: 9000 server.servlet.context-path: /servicessecurity.oauth2.client.clientId: myClientIdsecurity.oauth2.client.clientSecret: myClientSecretsecurity.oauth2.client.authorized-grant-types: authorization_code,refresh_token,password,client_credentialssecurity.oauth2.client.scope:data_insert,data_update, data_delete, data_select
In service config file I' defining users(huan and joe) of my application with passwords. Of course, in real life they will not be defined in a code, but should be stored in DB.
ServiceConfig:
package com.demien.sboot.oauthserver; import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter; @Configurationpublic class ServiceConfig extends GlobalAuthenticationConfigurerAdapter { @Override public void init(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("huan").password("{noop}sebastyan").roles("USER") .and() .withUser("joe").password("{noop}black").roles("USER", "ADMIN"); } }
In server runner application we have to define endpoint for getting user details - we will use it later.
Server runner application:
package com.demien.sboot.oauthserver; import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; import java.security.Principal; @SpringBootApplication@EnableAuthorizationServer@EnableResourceServer@RestControllerpublic class OauthServerApp { public static void main(String[] args) { SpringApplication.run(OauthServerApp.class, args); } @RequestMapping("/user") public Principal user(Principal user) { return user; }}
2. Resource server
Resource server is a spring boot application which has some protected resource(endpoint "/mydata"), which is not accessible without authorization. Client should provide authorization token to call this endpoint. In properties file we should define endpoint from authorization server mentioned above.application.properties
server.port=9001server.servlet.context-path=/servicessecurity.oauth2.resource.userInfoUri:http://localhost:9000/services/user
Service config:
package com.demien.sboot.service; import org.springframework.context.annotation.Configuration;import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; @Configuration@EnableGlobalMethodSecurity(prePostEnabled = true) public class ServiceConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { return new OAuth2MethodSecurityExpressionHandler(); } }
In main class we're just defining endpoint and class with some very important data.
Application runner:
package com.demien.sboot.service; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;import org.springframework.context.annotation.Bean;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; @SpringBootApplication@RestController@EnableResourceServerpublic class ServiceApp { public static void main(String[] args) { SpringApplication.run(ServiceApp.class, args); } @RequestMapping("/mydata") public ArrayList<MyData> getTollData() { ArrayList<MyData> result = new ArrayList<MyData>(); result.add(new MyData(1L, "one")); result.add(new MyData(2L, "two")); result.add(new MyData(3L, "three")); return result; } public class MyData { public final Long myId; public final String myValue; public MyData(Long myId, String myValue) { this.myId = myId; this.myValue = myValue; } public Long getMyId() { return myId; } public String getMyValue() { return myValue; } } }
3. Command line client
Client application is a most interesting thing here. First of all we have to define in properties detail for authorization.
application.yml
server: port: 9090 servlet: context-path: /services security: oauth2: client: clientId: myClientId clientSecret: myClientSecret accessTokenUri: http://localhost:9000/services/oauth/token userAuthorizationUri: http://localhost:9000/services/oauth/authorize clientAuthenticationScheme: form resource: userInfoUri: http://localhost:9000/services/user preferTokenInfo: false
In main application runner we are about to call our protected endpoint: http://localhost:9001/services/mydata
But for this call we should be authorized first. I'm using credentials of user "joe" for this.
Also I'm printing authorization token, to make sure we have it.
And finally I'm calling this endpoint.
Application runner:
package com.demien.sboot.client; import org.springframework.boot.CommandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.security.oauth2.client.OAuth2RestTemplate;import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;import org.springframework.security.oauth2.common.AuthenticationScheme; import java.util.Arrays; @SpringBootApplicationpublic class CommandLineApp implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(CommandLineApp.class, args); } @Override public void run(String... args) throws Exception { System.out.println("starting"); ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails(); resourceDetails.setAuthenticationScheme(AuthenticationScheme.header); resourceDetails.setAccessTokenUri("http://localhost:9000/services/oauth/token"); resourceDetails.setScope(Arrays.asList("data_select")); resourceDetails.setClientId("myClientId"); resourceDetails.setClientSecret("myClientSecret"); resourceDetails.setUsername("joe"); resourceDetails.setPassword("black"); OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails); String token = restTemplate.getAccessToken().getValue(); System.out.println("token:" + token); String s = restTemplate.getForObject("http://localhost:9001/services/mydata", String.class); System.out.println("Result:" + s); } }
4. Execution
First of all we should start Authorization and resource services.After that, we're free to go: let's start our client.
For me it produced next output:
starting
token:2b9560c9-355d-4134-a63b-e10f05b1b9b4
Result:[{"myId":1,"myValue":"one"},{"myId":2,"myValue":"two"},{"myId":3,"myValue":"three"}]
It looks simple, but under the hood client called authorization server, to get token, called resource service with token saved in session. Resource service called authorization server again to validate the token and after that - returned result back to client.