Oauth 2 Centralized Authorization with Spring Boot 2.0.2 and Spring Security 5 and JDBC token store

In this post we will see how to use Spring Boot 2 together with Spring Security 5 OAuth2 to implement an authorization server for centralized authorization and how to administrate it through a GUI also a resource server demo will be provided as well as the whole project under github.

A lot of examples cover the implementation of Oauth2 using in-memory tokens based on earlier versions of Spring boot 2 and Spring Security 5, so the idea is to use a MySql database as a token store.

To get deep into the topic we will have to:

  • Configure Spring Security .
  • Configure the database.
  • Create an Authorization Server.
  • Create a Resource Server.
  • Get a secured Resource using an access token using a curl client.

What is Oauth 2 ?

OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 supersedes the work done on the original OAuth protocol created in 2006. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This specification and its extensions are being developed within the IETF OAuth Working Group.

Oauth 2 roles

OAuth2 defines 4 roles :

  • Resource Owner: generally yourself.
  • Resource Server: server hosting protected data (for example Google hosting your profile and personal information).
  • Client: application requesting access to a resource server ( website, a Javascript application or a mobile application…).
  • Authorization Server: server issuing access token to the client. This token will be used for the client to request the resource server. This server can be the same as the resource server (same physical server and same application), and it is often the case.

the following figure illustrates the roles flow :

Oauth2 roles

Grant Types

OAuth 2 provides several “grant types” for different use cases. The grant types defined are:

  • Authorization Code : The authorization code grant is the feature of signing into an application using your Facebook or Google account.
  • Password: It is intended to be used for user-agent-based clients (e.g. single page web apps) that can’t keep a client secret.Secondly instead of the authorization server returning an authorization code which is exchanged for an access token as the case of Authorization Code grant, the authorization server returns an access token.
  • Client credentials : The client can request an access token using only its client credentials (or other supported means of authentication) when the
     client is requesting access to the protected resources under its
     control, or those of another resource owner that have been previously
     arranged with the authorization server.
  • Implicit: The implicit grant is a simplified authorization code flow optimized for clients implemented in a browser using a scripting language such as JavaScript. In the implicit flow, instead of issuing the client
     an authorization code, the client is issued an access token directly.

Demonstration

let’s get our hands dirty

Business Layer

For simplicity reasons our main business application will be a product API using one entity and our access rules will be :

  • PRODUCT_CREATE
  • PRODUCT_UPDATE
  • PRODUCT_DISPLAY
  • PRODUCT_ADMIN

OAuth2 Client Setup

To set up Oauth 2 clients we need to create the following tables [see links for more details]

we will call a resource server like ‘product_api’ For this server, we define one client called:

  • read-write-client (authorized grant types: read, write)
INSERT INTO OAUTH_CLIENT_DETAILS(CLIENT_ID, RESOURCE_IDS, CLIENT_SECRET, SCOPE, AUTHORIZED_GRANT_TYPES, AUTHORITIES, ACCESS_TOKEN_VALIDITY, REFRESH_TOKEN_VALIDITY)
VALUES ('read-write-client', 'product-api','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','read,write', 'client_credentials', 'ROLE_PRODUCT_ADMIN', 10800, 2592000);
#password [hashed with BCCrypt] :user

Authorities and Users Setup

Spring Security comes with two useful interfaces:

  • UserDetails — provides core user information.
  • GrantedAuthority — represents an authority granted to an Authentication object.

below is the script that will load all authorities and credentials (users ):

INSERT INTO authority  VALUES(1,'ROLE_OAUTH_ADMIN');
INSERT INTO authority VALUES(2,'ROLE_ADMIN_PRODUCT');
INSERT INTO authority VALUES(3,'ROLE_RESOURCE_ADMIN');
INSERT INTO credentials VALUES(1,b'1','oauth_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0');
INSERT INTO credentials VALUES(2,b'1','resource_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0');
INSERT INTO credentials VALUES(3,b'1','user','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0');
INSERT INTO credentials_authorities VALUES (1, 1);
INSERT INTO credentials_authorities VALUES (2, 3);
INSERT INTO credentials_authorities VALUES (3, 2);
#Password : user

API Layer

For the demo a RESTful application is developped based on spring boot and exposing the following endpoints:

Spring Security Configuration

We have to provide an implementation of the UserDetailsService interface in order to get our users credentials and authorities as following

To provide security to the application we will use @EnableWebSecurity annotation and WebSecurityConfigurerAdapter

package com.aak.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

/**
* Created by ahmed on 20.5.18.
*/
@EnableWebSecurity
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {


@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new JdbcUserDetails();
}

@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/webjars/**");

}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login","/logout.do").permitAll()
.antMatchers("/**").authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login.do")
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/login")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout.do"))
.and()
.userDetailsService(userDetailsServiceBean());
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean())
.passwordEncoder(passwordEncoder());
}


}

OAuth2 Configuration

To set up Oauth 2 two components are needed to be implemented

  • Authorization Server
  • Resource Server

Authorization Server

The authorization server is responsible for the verification of user identity and providing the tokens ,the use of @EnableAuthorizationServer annotation enables the Authorization server configuration

package com.aak.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
* Created by ahmed on 21.5.18.
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource oauthDataSource() {
return DataSourceBuilder.create().build();
}

@Bean
public JdbcClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(oauthDataSource());
}

@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(oauthDataSource());
}

@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(oauthDataSource());
}

@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(oauthDataSource());
}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {

}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.approvalStore(approvalStore())
.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore());
}
}

After a sucessful configuration run the Autorization server, you will get the login page to administrate the Authozation server

Oauth2 server login page

Use oauth_admin/user as username/password to access to the Oauth2 dashboard where you can create your server clients

Oauth2 Dashboard

Resource Server

A Resource Server hosts resources that are protected by the OAuth2 token (basically it is our Product API )

package com.aak.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
* Created by ahmed on 30.5.18.
*/
@EnableResourceServer
@Configuration
public class ResourcesServerConfiguration extends ResourceServerConfigurerAdapter {

@Bean
@ConfigurationProperties(prefix="spring.datasource")
public DataSource ouathDataSource(){return DataSourceBuilder.create().build();}

@Override
public void configure(ResourceServerSecurityConfigurer resources)throws Exception{

TokenStore tokenStore=new JdbcTokenStore(ouathDataSource());
resources.resourceId("product_api").tokenStore(tokenStore);

}
@Override

public void configure(HttpSecurity http) throws Exception{


http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
.and()

.headers().addHeaderWriter((request, response) -> {
response.addHeader("Access-Control-Allow-Origin", "*");
if (request.getMethod().equals("OPTIONS")) {
response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
}
});
}
}

Test the products list endpoint withthe following Curl client:

#!/bin/sh
TOKEN=`curl -s -u curl_client:user -X POST localhost:8081/oauth/token\?grant_type=client_credentials | egrep -o ‘[a-f0–9-]{20,}’`
echo “ tGot token for curl client as :$TOKEN”
curl localhost:8083/product/products -H “Authorization: Bearer $TOKEN”

and the response is after runing the Curl client bash script:

$ ./client.sh
Got token for curl client as : 3be01519–0cab-4049-b87d-617c48bda502
[{“version”:0,”name”:”product_1",”available”:false},{“version”:0,”name”:”product_2",”available”:true}]

Checkout the whole code from github :

Ref :