Spring Boot OAuth 2.0 separating Authorization Service and Resource Service

Buddhi Prabhath
3 min readOct 4, 2019

--

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.

git repo: https://github.com/buddhiprab/springboot-oauth2-separating-authorization_server-and-resource_server.git

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

--

--

Buddhi Prabhath
Buddhi Prabhath

No responses yet