Categories: development

Spring Security OAuth2

Spring Security makes it easy to implement OAuth2 as your protocol for authentication. In this article, we are going to implement an authentication server using Spring Security OAuth2. We are also going to implement a very basic client which will make use of the authentication server.

Prerequisites

The following is required in order to follow along the article.

  • Java 8
  • Maven (Gradle is fine too, but examples will use Maven)

Creating the projects

We are going to create two projects. The first one will be the authentication server, and the other one will be the client which will have a basic user interface and use the authentication server. It’s possible to have these both on the same server but I believe it’s easier to understand the examples if they are implemented separately, this also means that it will be much easier to switch to another authentication server in the future, for example, facebook or google.

Start by heading to Spring Initializr. I am creating a Maven application with Java and Spring Boot 1.5.9.

I uploaded the two projects to GitHub if you would like to inspect the final code; Authorization Server and Authorization Client.

Authentication Server

For the authentication add the following dependencies:

  • Cloud OAuth2
  • Web

I am also going to go ahead and add Couchbase as a dependency as it’s my preferable database, but you can, of course, use any database that you would like.

Authentication Client

For the authentication client you will want to have the following dependencies:

  • Cloud OAuth2
  • Web
  • Thymeleaf

Authentication Server

Configuring Spring Security OAuth2 Authentication Server

We are going to start with implementing the Spring Security OAuth2 Authentication Server.

Under a package named config create a class AuthorizationServerConfig.


package org.thecuriousdev.authserver.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

  private AuthenticationManager authenticationManager;

  @Autowired
  public AuthorizationServerConfig(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
  }

  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security.tokenKeyAccess("permitAll()")
        .checkTokenAccess("isAuthenticated()");
  }

  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
        .withClient("ClientId")
        .secret("secret")
        .authorizedGrantTypes("authorization_code")
        .scopes("user_info")
        .autoApprove(true);
  }

  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.authenticationManager(authenticationManager);
  }
}

The annotation @EnableAuthorizationServer enables an AuthorizationEndpoint and a TokenEndpoint, and we also extend AuthorizationServerConfigurerAdapter to be able to override some configuration that we need. In configure(ClientDetailsServiceConfigurer) we specify that we want to store the necessary data for creating sessions in memory. We choose a pretty basic clientId and secret in the example, but if you are taking this into production then I recommend setting it to something more appropriate. People should not be able to guess this value. There are some different authorizedGrantTypes that we can specify, but authorization_code is considered a “classic” OAuth2 flow. If you are interested in reading about different grant types, then I recommend checking out Authorization Grant. Additionally, we configure autoApprove to be true in order to disable the user approval process and do a redirect directly.

ResourceServerConfig

Our authentication server is also going to act as a resource server and therefore we need to create that configuration.


package org.thecuriousdev.authserver.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.thecuriousdev.authserver.service.CustomUserDetailsService;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private AuthenticationManager authenticationManager;

  @Autowired
  private CustomUserDetailsService customUserDetailsService;

  @Autowired
  public ResourceServerConfig(AuthenticationManager authenticationManager, 
      CustomUserDetailsService customUserDetailsService) {
    this.authenticationManager = authenticationManager;
    this.customUserDetailsService = customUserDetailsService;
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.requestMatchers()
        .antMatchers("/login", "/oauth/authorize")
        .and()
        .authorizeRequests()
        .anyRequest()
        .authenticated()
        .and()
    .formLogin()
    .permitAll();
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.parentAuthenticationManager(authenticationManager)
        .userDetailsService(customUserDetailsService);
  }
}

Pretty standard stuff, we declare two antMatchers “/login” and “/oauth/authorize”, and we also let Spring generate a login page that everyone is permitted to visit. For the AuthenticationManager we pass in a CustomUserDetailsService which is a class we created in order to make it possible to read the user from the database. The CustomUserDetailsService can make use of any database. The implementation is very dependant on which type of database that you use, but I will give you mine as an example and it should be very straightforward for you to retrieve the user from any database of your choice.


package org.thecuriousdev.authserver.service;

import java.util.Arrays;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.thecuriousdev.authserver.entity.User;
import org.thecuriousdev.authserver.repository.UserRepository;
import org.thecuriousdev.authserver.util.CustomUserDetails;

@Service
public class CustomUserDetailsService implements UserDetailsService {

  private UserRepository userRepository;

  @Autowired
  public CustomUserDetailsService(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  @PostConstruct
  public void test() {
    User user = new User();
    user.setUsername("planevik");
    user.setPassword("password");
    user.setRoles(Arrays.asList("ADMIN", "USER"));

    if (!userRepository.findById("planevik").isPresent()) {
      userRepository.create(user);
    }
  }

  @Override
  public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
    return new CustomUserDetails(userRepository.findById(id)
        .orElseThrow(() -> new UsernameNotFoundException("User not found")));
  }
}

Our class implements the UserDetailsService which forces us to implement the loadUserByUsername method where we have to return UserDetails for the user. Because of this, we also create a CustomerUserDetails class which can look like the following. If you are interested in implementing a Couchbase as your database, I recommend checking out my previous article, NOSQL with Couchbase: Getting Started, where I basically implement a very similar repository. Of course, you can also check out the GitHub source code for this article.


package org.thecuriousdev.authserver.util;

import java.util.Collection;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.thecuriousdev.authserver.entity.User;

public class CustomUserDetails implements UserDetails {

  private String username;
  private String password;
  private Collection extends GrantedAuthority> authorities;

  public CustomUserDetails(User user) {
    this.username = user.getUsername();
    this.password = user.getPassword();
    this.authorities = user.getRoles().stream()
        .map(SimpleGrantedAuthority::new)
        .collect(Collectors.toList());
  }

  @Override
  public Collection extends GrantedAuthority> getAuthorities() {
    return authorities;
  }

  @Override
  public String getPassword() {
    return password;
  }

  @Override
  public String getUsername() {
    return username;
  }

  @Override
  public boolean isAccountNonExpired() {
    return true;
  }

  @Override
  public boolean isAccountNonLocked() {
    return true;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return true;
  }

  @Override
  public boolean isEnabled() {
    return true;
  }
}

Our class implements the UserDetails interface and implements the necessary methods.
We have a User entity which we create a convenient constructor for that will construct our CustomUserDetails class from it. We leave out the implementation of account expiration, locked and enabled by simply returning true.

Returning a resource

When a user has authenticated we want the resource server to return a resource so that we can check if it worked. We create a class ResourceController.


package org.thecuriousdev.authserver;

import java.security.Principal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/rest/resource")
public class ResourceController {

  @GetMapping
  public Principal user(Principal principal) {
    return principal;
  }
}

We return the principal which also makes it possible to inspect the name of the authenticated user.

Application configuration

The last thing we need is some application configuration. I created the application.yml under our resources folder.


server:
  port: 12050
  context-path: /auth

security.basic.enable: false

We specify the context-path /auth to make it more clear that it’s the authentication server.

Spring Security OAuth2 Authentication Client

We move on to our second server which will make use of the Spring Security OAuth2 authentication server that we have created. The client application will be a very basic UI application with an index page instructing people to press a link to log in where they get redirected to the login form of the authentication server to retrieve an authorization token. This token will be stored as a cookie and used when retrieving resources from the resource server.

OAuthConfig

In order to enable OAuth2 as single sign on we can use the @EnableOAuth2Sso. We also extend the WebSecurityConfigurerAdapter in order to override configure(HttpSecurity).


package org.thecuriousdev.authclient.config;

import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableOAuth2Sso
public class OAuthConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher("/**")
        .authorizeRequests()
        .antMatchers("/", "/login**")
        .permitAll()
        .anyRequest()
        .authenticated();
  }
}

Creating templates

We are going to create two templates, one index template which will be open to everyone. The second one, will be a secured template which requires a valid authentication token.

Under resources/templates, create the two following templates.

index.html




  
  



  

Spring Security OAuth2 Example


  Login via OAuth2 here



secure.html





  
  



Spring Security OAuth2 Example - Secured


Welcome to the secured page!

Creating the controller for displaying the templates

Create a controller like the following in order to forward the request to the appropriate template that we created.



package org.thecuriousdev.authclient.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class BasicController {

  @GetMapping
  public String index() {
    return "index";
  }

  @GetMapping("/secure")
  public String secure() {
    return "secure";
  }
}

Application configuration

Our Spring Security OAuth2 client application also needs some configuration.


server:
  port: 12051
  context-path: /ui
  session:
    cookie:
      name: UISESSION

security:
  basic:
    enabled: false
  oauth2:
    client:
      clientId: ClientId
      clientSecret: secret
      accessTokenUri: http://localhost:12050/auth/oauth/token
      userAuthorizationUri: http://localhost:12050/auth/oauth/authorize
    resource:
      userInfoUri: http://localhost:12050/auth/rest/resource

spring:
  thymeleaf:
    cache: false

We assign a context-path of /ui so that it is more clear that it is the user interface. We also give the name “UISESSION” to our session cookie. In this cookie, the authentication token will be stored that we retrieved from the authentication server. In our OAuth2 client configuration, the clientId and secret have to match to what we created in the authentication server, and we also assign the URI for retrieving the token and on what URI the user should perform the authorization. Additionally, we specify what URL should be used to retrieve the user information. All of this has to match with what we created in the authentication server.

Thymeleaf-Extras-SpringSecurity4

In order to inspect the authorization object (and use more nice functionalities with Spring Security via Thymeleaf, the following dependency should be added to both the server and the client.



  org.thymeleaf.extras
  thymeleaf-extras-springsecurity4


Trying it out

If we now boot both servers up and hit the client application on http://localhost:12051/ui we should be presented with the index page where we can press to log in with OAuth2. Log in with OAuth2 should redirect us to http://localhost:12050/auth/login where we can enter our user credentials. Assuming that you have implemented a CustomerDetailService with a database of your choice, you should be able to login with the details that we created in our @PostConstruct in that service. Doing that redirects us back to our client application http://localhost:12051/ui/secure and we have now accessed a secured template.

If we inspect our cookies in the browser you should now see a UISESSION cookie that was created which stores the authentication token that we received from our Spring Security OAuth2 authentication server.

Final words

We have created an Authentication Server which implements both an AuthorizationServer and a ResourceServer. Additionally, we created an Authentication Client application which uses the Authentication Server as the security mechanism. The Authentication Server that we created is basically the same type of service that Facebook and Google provides (but of course a lot primitive). Because we developed a separate client application, it is also very easy to switch it to use an existing trusted authentication server (such as Facebook or Google).

snieking

Share
Published by
snieking
Tags: authenticationoauth2securityspring

Recent Posts

  • development
  • java

Handle Stream Exceptions with an Attempt

Streams has become a very popular way to process a collection of elements. But a…

2 years ago
  • deployment
  • development

Deploying Spring Boot in Pivotal Cloud Foundry

A lot of focus on my previous blogs has been on how to build micro…

2 years ago
  • python

Working with High-Quality Reference Genomes

Learn how to work with high-quality reference genomes in this article by Tiago Antao, a…

2 years ago
  • java

Garbage Collection in JDK 12 and onward

Garbage collection is one of the key concepts of Java programming and up to now…

2 years ago
  • python

Understanding Convolution

Learn about convolution in this article by Sandipan Dey, a data scientist with a wide…

2 years ago
  • java

Lombok Builder with Jackson

Lombok comes with a very convenient way of creating immutable objects with the builder pattern.…

2 years ago