Spring Security — OAuth2 — MongoDB

The main purpose of this story is to show how to secure a spring boot microservice using spring security,Oauth2 and MongoDB.

Photo by Jon Moore on Unsplash

Prerequisites

  • Spring Boot 2.4
  • Maven 3.6.+
  • Java 11
  • Mongo 4.4

Oauth2 Overview

The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf. This specification replaces and obsoletes the OAuth 1.0 protocol described in RFC 5849. — https://datatracker.ietf.org/doc/html/rfc6749

OAuth2 Roles:

There are four roles that can be applied on OAuth2:

  • Resource Owner: The owner of the resource — When the resource owner is a person, it is referred to as an end-user.
  • Resource Server: The server hosting the protected resources by the OAuth2 token.
  • Client: The application requesting an access token.
  • Authorization Server: This is the server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization.
Protocol Flow

Getting Started

We will start by creating a simple Spring Boot project from start.spring.io, with the following dependencies: Web, MongoDB, and Lombok.

To start by configuring spring cloud oauth2 in our project, we need to add the dependencies below in the pom.xml file.

  • spring-cloud-starter-oauth2 dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
  • spring-cloud-starter-security dependency
<dependency>
<
groupId>org.springframework.cloud</groupId>
<
artifactId>spring-cloud-starter-security</artifactId>
</
dependency>
  • Spring cloud dependencyManagement
<dependencyManagement>
<
dependencies>
<
dependency>
<
groupId>org.springframework.cloud</groupId>
<
artifactId>spring-cloud-dependencies</artifactId>
<
version>${spring-cloud.version}</version>
<
type>pom</type>
<
scope>import</scope>
</
dependency>
</
dependencies>
</
dependencyManagement>

OAuth2 Mongo Collections

  • oauth_client_details: The mongo Collection stores details about OAuth2 client applications. Every Web Flow client application should have a record in this table.
  • oauth_client_token: The mongo Collection stores OAuth2 tokens for retrieval by client applications.
  • oauth_access_token: The mongo Collection stores OAuth2 access tokens.
  • oauth_refresh_token: The mongo Collection stores OAuth2 refresh tokens.
  • oauth_code: The mongo Collection oauth_code stores data for the OAuth2 authorization code grant.
  • oauth_approvals: The mongo Collection oauth_approvals stores approval status, supports for Authorization Code flow.

Resource Mongo Collections

We are going to create the model classes representing the collections of oauth2 and resources. Let’s start the configuration.

Authorization Server Setup

Add @EnableAuthorizationServer to authorization server’s configuration class to designate this class as the authorization server.

@Configuration
@EnableAuthorizationServer
@Import(ServerWebSecurityConfig.class)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;

@Resource(name = "userService")
private UserDetailsService userDetailsService;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.pathMapping("/oauth/authorize", "/api/oauth/authorize")
.pathMapping("/oauth/check_token", "/api/oauth/check_token")
.pathMapping("/oauth/confirm_access", "/api/oauth/confirm_access")
.pathMapping("/oauth/error", "/api/oauth/error")
.pathMapping("/oauth/token", "/api/oauth/token")
.tokenStore(tokenStore())
.reuseRefreshTokens(false)
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.exceptionTranslator(new CustomWebResponseExceptionTranslator());
}


@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
// Expose the verifier key endpoint "/oauth/token_key" to the public for validation of the JWT token
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}

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

@Bean
public OAuthClientDetailsService clientDetailsService() {
return new OAuthClientDetailsService(null);
}

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

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new CustomTokenEnhancer();
converter.setSigningKey("w8y35rr1x04x1fw9");
return converter;
}

@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}


}

In this class, we have two important bean which use data which is stored in the mongo database.

clientDetailsServices holds all of the information required to the oauth client details.

tokenStore() For generating and retrieving tokens.

Resource Server Setup

To use the access token we need a Resource Server (which can be the same as the Authorization Server). Creating a Resource Server is easy, just add @EnableResourceServer.

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

private static final String RESOURCE_ID = "resource_id";

@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);

// Override exception formatting by injecting the accessDeniedHandler & authenticationEntryPoint
// custom exception for resource server
resources.authenticationEntryPoint(new CustomAuthenticationEntryPoint());
resources.accessDeniedHandler(new CustomAccessDeniedHandler());
}

@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/api/**").authenticated();
http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
http.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());

}


}

Testing authentication endpoints

With these basic configurations, Let’s check if our authentication endpoints is working.

we need to insert the data into the database for initialization before testing.

Run this script in mongo instance:

db.oauth_client_details.insert(
[
{
"clientId" : "oauth_client_id",
"resourceIds" : [ "resource_id"],
"secretRequired" : true,
"clientSecret" : "$2a$10$vRo/pkgrVqQvtZPRE6NOcOcwjnojPzNCmzzco2bE5L9RxbGwG29I.",
"scoped" : true,
"scope" : [ "role_admin", "role_user"],
"authorizedGrantTypes" : [
"password",
"authorization_code",
"refresh_token",
"implicit"
],
"authorities" : [],
"accessTokenValiditySeconds" : NumberInt(3600),
"refreshTokenValiditySeconds" : NumberInt(21600),
"autoApprove" : false,
"additionalInformation" : {},
"_class" : "com.security.rad.springbootoauth2mongodb.domain.document.OAuthClientDetails"
}
]
);


db.permission.insert(
[
{
"_id" : ObjectId("60a26bea148fa508b1f1017f"),
"name" : "CREATE_SET_TECH",
"_class" : "com.microservice.springsecurityoauth2mongodb.document.Permission"
}
]
);

db.group_role.insert(
[
{
"_id" : ObjectId("60a26c7a66d02867993b04d2"),
"name" : "Administrator",
"code" : "MD_SET_ADMIN",
"description" : "Account setup",
"permissions" : ["CREATE_SET_TECH"],
"_class" : "com.microservice.springsecurityoauth2mongodb.document.GroupRole"
}
]
);

db.app_user.insert(
[
{
"accountNonExpired" : false,
"accountNonLocked" : false,
"credentialsNonExpired" : false,
"email" : "boottechnologies.ci@gmail.com",
"enabled" : true,
"firstName" : "system",
"lastName" : "admin",
"password" : "$2a$04$EZzbSqieYfe/nFWfBWt2KeCdyq0UuDEM1ycFF8HzmlVR6sbsOnw7u",
"username" : "system",
"roles" : ["MD_SET_ADMIN"],
"_class" : "com.microservice.springsecurityoauth2mongodb.document.User"
}
]
);

/api/oauth/token — for obtaining the token.

/api/oauth/token —for refreshing an access token.

Summary

In this story, we showed how to secure a Spring Boot microservice using Spring Security, Oauth2, and MongoDB.

The thing to note is that Spring Security OAuth 2.4.0 officially deprecates all its classes. See the OAuth 2.0 Migration Guide for Spring Security 5.

In April 2020, Spring security team announced the Spring Authorization Server project. It’s delivering Authorization Server support to the Spring community.

The complete source code can be found in my GitHub repository.

References

  1. https://alexbilbie.com/guide-to-oauth-2-grants/
  2. https://oauth.net/2/
  3. https://datatracker.ietf.org/doc/html/rfc6749
  4. https://javadoc.io/doc/org.springframework.security/spring-security-core/5.2.0.RELEASE/index.html

Software Engineer