Spring Boot OAuth 2.0 separating Authorization Service and Resource Service
The provider role in OAuth 2.0 is actually split between Authorization Service and Resource Service, and while these sometimes reside in the same application, with Spring Security OAuth you have the option to split them across two applications, and also to have multiple Resource Services that share an Authorization Service.
drawn using: https://www.draw.io/
step 1 - request token for client “a” (client username=a, client secret=a)
POST /oauth/token?grant_type=client_credentials HTTP/1.1
Host: localhost:8100
Authorization: Basic YTph //base64 encode a:a
step 2 - Authorization Server will verify the client credential and respods with token
{
"access_token": "d200a90e-3585-498c-95cd-7a0cc1be65c0",
"token_type": "bearer",
"expires_in": 43199,
"scope": "all"
}
step 3 - request Resource Server endpoint /account with above received token
GET /account HTTP/1.1
Host: localhost:2222
Authorization: Bearer d200a90e-3585-498c-95cd-7a0cc1be65c0
step 4 - step3 will trigger oauth/check_token request from Resource Server to Authentication Server to verify the token with client “b” credentials as basic authentication (client username: b, client secret: b), need to use client “b” to request oauth/check_token because it’s secured, client should have ROLE_C authority to access this endpoint
POST /oauth/check_token?token=d200a90e-3585-498c-95cd-7a0cc1be65c0 HTTP/1.1
Host: localhost:8100
Authorization: Basic Yjpi //base64 encode b:b
step 5 - Authentication Server will response with client “a” authorities for the given token
{
"scope": [
"all"
],
"active": true,
"exp": 1570197641,
"authorities": [
"ROLE_TRUSTED_CLIENT",
"ROLE_A",
"ROLE_B"
],
"client_id": "a"
}
step 6 - since /account end point is secured with ROLE_A and the given token has that authority, now Resource Server will execute and respond with /account endpoint response
account 1
Authorization Server Configuration
package com.buddhi.demos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
@SpringBootApplication
@EnableAuthorizationServer
public class AuthorizationServer {
public static void main(String[] args) {
SpringApplication.run(AuthorizationServer.class, args);
}
}
extend AuthorizationServerConfigurerAdapter to configure Authorization Server, specifing 2 clients a, b where client a having ROLE_A, ROLE_B etc…client b will be used to call oauth/check_token end point in the Resource Server
package com.buddhi.demos;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("a")//client username: a
.secret(passwordEncoder().encode("a"))//password: a
.authorities("ROLE_A","ROLE_B","ROLE_TRUSTED_CLIENT")
.scopes("all")
.authorizedGrantTypes("client_credentials")
.and()
.withClient("b")//client username: b
.secret(passwordEncoder().encode("b")) password: b
.authorities("ROLE_C")
.scopes("all")
.authorizedGrantTypes("client_credentials");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// security.checkTokenAccess("permitAll()"); //if used permitAll() this will make oauth/check_token endpoint to be unsecure
security.checkTokenAccess("hasAuthority('ROLE_C')"); //secure the oauth/check_token endpoint
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(4);
}
}
application.yml
server:
port: 8100
spring:
application:
name: auth-server
logging:
level:
org.springframework.security: TRACE
i’m using maven multi module structure for this project, you can find them in the git repo
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>authorization-server</artifactId>
<parent>
<artifactId>spring-boot-oauth2-examples</artifactId>
<groupId>com.buddhi</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
</project>
Resource Server Configuration
using ResourceServerTokenServices bean which returns a RemoteTokenServices object to check token, note the client “b” is used to call oauth/check_token endpoint
package com.buddhi.demos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableResourceServer
@RestController
public class ResourceServer {
public static void main(String[] args) {
SpringApplication.run(ResourceServer.class, args);
}
@GetMapping("/account")
public String getAccount() {
return "account 1";
}
@Bean
public ResourceServerTokenServices tokenService() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId("b");
tokenServices.setClientSecret("b");
tokenServices.setCheckTokenEndpointUrl("http://localhost:8100/oauth/check_token");
return tokenServices;
}
}
configure permissions for /account end point, in this example client having “ROLE_A” can access /account endpoint
package com.buddhi.demos;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@Configuration
public class ResourceServerEndpointConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/account").hasAuthority("ROLE_A");
}
}
application.yml
server:
port: 2222
spring:
application:
name: resource-server
logging:
level:
org.springframework.security: TRACE
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>resource-server</artifactId>
<parent>
<artifactId>spring-boot-oauth2-examples</artifactId>
<groupId>com.buddhi</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
</project>
parent pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.buddhi</groupId>
<artifactId>spring-boot-oauth2-examples</artifactId>
<version>1.0-SNAPSHOT</version>
<description>Spring Boot oauth2 separating authorization server and resource server</description>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
<modules>
<module>authorization-server</module>
<module>resource-server</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
references:
https://projects.spring.io/spring-security-oauth/docs/oauth2.html#resource-server-configuration