Back-end9 minute read

Using Spring Boot for OAuth2 and JWT REST Protection

REST APIs are used in every language and on every platform. Building a secure REST API is a must-have tool in every developer’s arsenal.

In this article, Toptal Freelance Java Developer Sergio Moretti shows how to secure a REST API using Spring Boot.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

REST APIs are used in every language and on every platform. Building a secure REST API is a must-have tool in every developer’s arsenal.

In this article, Toptal Freelance Java Developer Sergio Moretti shows how to secure a REST API using Spring Boot.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Sergio Moretti
Verified Expert in Engineering
22 Years of Experience

Sergio has over a dozen years of experience developing enterprise-level applications with Java and RDBMS like Oracle, PostgreSQL, and MySQL.

Expertise

Share

This article is a guide on how to setup a server-side implementation of JSON Web Token (JWT) - OAuth2 authorization framework using Spring Boot and Maven.

An initial grasp on OAuth2 is recommended and can be obtained reading the draft linked above or searching for useful information on the web like this or this.

OAuth2 is an authorization framework superseding it first version OAuth, created back in 2006. It defines the authorization flows between clients and one or more HTTP services in order to gain access to protected resources.

OAuth2 defines the following server-side roles:

  • Resource Owner: The service responsible for controlling resources’ access
  • Resource Server: The service who actually supplies the resources
  • Authorization Server: The service handling authorization process acting as a middleman between client and resource owner

JSON Web Token, or JWT, is a specification for the representation of claims to be transferred between two parties. The claims are encoded as a JSON object used as the payload of an encrypted structure, enabling the claims to be digitally signed or encrypted.

The containing structure can be JSON Web Signature (JWS) or JSON Web Encryption (JWE).

JWT can be chosen as the format for access and refresh tokens used inside the OAuth2 protocol.

OAuth2 and JWT gained a huge popularity over the last years because of the following features:

  • Provides a stateless authorization system for stateless REST protocol
  • Fits well in a micro-service architecture in which multiple resource servers can share a single authorization server
  • Token content easy to manage on client’s side due to JSON format

However, OAuth2 and JWT are not always the best choice in case the following considerations are important for the project:

  • A stateless protocol doesn’t permit access revocation on the server side
  • Fixed lifetime for token add additional complexity for managing long-running sessions without compromising security (e.g. refresh token)
  • A requirement for a secure store for a token on the client side

Expected Protocol Flow

While one of the main features of OAuth2 is the introduction of an authorization layer in order to separate authorization process from resource owners, for the sake of simplicity, the article’s outcome is the build of a single application impersonating all resource owner, authorization server, and resource server roles. Because of this, the communication will flow between two entities only, the server and the client.

This simplification should help to focus on the aim of the article, i.e. the setup of such a system in a Spring Boot’s environment.

The simplified flow is described below:

  1. Authorization request is sent from client to server (acting as resource owner) using password authorization grant
  2. Access token is returned to the client (along with refresh token)
  3. Access token is then sent from client to server (acting as resource server) on each request for protected resource access
  4. Server responds with required protected resources

Authentication flow diagram

Spring Security and Spring Boot

First of all, a brief introduction to the technology stack selected for this project.

The project management tool of choice is Maven, but due to the project’s simplicity, it should not be difficult to switch to other tools like Gradle.

In the article’s continuation, we focus on Spring Security aspects only, but all code excerpts are taken from a fully working server-side application which source code is available in a public repository along with a client consuming its REST resources.

Spring Security is a framework providing an almost declarative security services for Spring-based applications. Its roots are from the first beginning of Spring and it’s organized as a set of modules due to the high number of different security technologies covered.

Let’s take a quick look at Spring Security architecture (a more detailed guide can be found here).

Security is mostly about authentication, i.e. the verification of the identity, and authorization, the grant of access rights to resources.

Spring security supports a huge range of authentication models, either provided by third parties or implemented natively. A list can be found here.

Regarding authorization, three main areas are identified:

  1. Web requests authorization
  2. Method level authorization
  3. Access to domain object instances authorization

Authentication

The basic interface is AuthenticationManager which is responsible to provide an authentication method. The UserDetailsService is the interface related to user’s information collection, which could be directly implemented or used internally in case of standard JDBC or LDAP methods.

Authorization

The main interface is AccessDecisionManager; which implementations for all three areas listed above delegate to a chain of AccessDecisionVoter. Each instance of the latter interface represents an association between an Authentication (a user identity, named principal), a resource and a collection of ConfigAttribute, the set of rules which describes how the resource’s owner allowed the access to the resource itself, maybe through the use of user roles.

The security for a web application is implemented using the basic elements described above in a chain of servlet filters, and the class WebSecurityConfigurerAdapter is exposed as a declarative way to express resource’s access rules.

Method security is first enabled by the presence of the @EnableGlobalMethodSecurity(securedEnabled = true) annotation, and then by the use of a set of specialized annotations to apply to each method to be protected such as @Secured, @PreAuthorize, and @PostAuthorize.

Spring Boot adds to all of this a collection of opinionated application configurations and third-party libraries in order to ease the development while maintaining an high quality standard.

JWT OAuth2 with Spring Boot

Let’s now move on the original problem to set up an application implementing OAuth2 and JWT with Spring Boot.

While multiple server-side OAuth2 libraries exist in the Java world (a list can be found here), the spring-based implementation is the natural choice as we expect to find it well integrated into Spring Security architecture and therefore avoid the need to handle much of the low-level details for its use.

All security-related library dependencies are handled by Maven with the help of Spring Boot, which is the only component requiring an explicit version inside maven’s configuration file pom.xml (i.e. library versions are automatically inferred by Maven choosing the most up-to-date version compatible with the inserted Spring Boot version).

Find below the excerpt from maven’s configuration file pom.xml containing the dependencies related to Spring Boot security:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>

The app acts both as OAuth2 authorization server/resource owner and as resource server.

The protected resources (as resource server) are published under /api/ path, while authentication path (as resource owner/authorization server) is mapped to /oauth/token, following proposed default.

App’s structure:

  • security package containing security configuration
  • errors package containing error handling
  • users, glee packages for REST resources, including model, repository, and controller

Next paragraphs cover the configuration for each one of the three OAuth2 roles mentioned above. The related classes are inside security package:

  • OAuthConfiguration, extending AuthorizationServerConfigurerAdapter
  • ResourceServerConfiguration, extending ResourceServerConfigurerAdapter
  • ServerSecurityConfig, extending WebSecurityConfigurerAdapter
  • UserService, implementing UserDetailsService

Setup for Resource Owner and Authorization Server

Authorization server behavior is enabled by the presence of @EnableAuthorizationServer annotation. Its configuration is merged with the one related to the resource owner behavior and both are contained in the class AuthorizationServerConfigurerAdapter.

The configurations applied here are related to:

  • Client access (using ClientDetailsServiceConfigurer)
    • Selection of use an in-memory or JDBC based storage for client details with inMemory or jdbc methods
    • Client’s basic authentication using clientId and clientSecret (encoded with the chosen PasswordEncoder bean) attributes
    • Validity time for access and refresh tokens using accessTokenValiditySeconds and refreshTokenValiditySeconds attributes
    • Grant types allowed using authorizedGrantTypes attribute
    • Defines access scopes with scopes method
    • Identify client’s accessible resources
  • Authorization server endpoint (using AuthorizationServerEndpointsConfigurer)
    • Define the use of a JWT token with accessTokenConverter
    • Define the use of an UserDetailsService and AuthenticationManager interfaces to perform authentication (as resource owner)
package net.reliqs.gleeometer.security;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

@Configuration
@EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {

   private final AuthenticationManager authenticationManager;

   private final PasswordEncoder passwordEncoder;

   private final UserDetailsService userService;

   @Value("${jwt.clientId:glee-o-meter}")
   private String clientId;

   @Value("${jwt.client-secret:secret}")
   private String clientSecret;

   @Value("${jwt.signing-key:123}")
   private String jwtSigningKey;

   @Value("${jwt.accessTokenValidititySeconds:43200}") // 12 hours
   private int accessTokenValiditySeconds;

   @Value("${jwt.authorizedGrantTypes:password,authorization_code,refresh_token}")
   private String[] authorizedGrantTypes;

   @Value("${jwt.refreshTokenValiditySeconds:2592000}") // 30 days
   private int refreshTokenValiditySeconds;

   public OAuthConfiguration(AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder, UserDetailsService userService) {
       this.authenticationManager = authenticationManager;
       this.passwordEncoder = passwordEncoder;
       this.userService = userService;
   }

   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
       clients.inMemory()
               .withClient(clientId)
               .secret(passwordEncoder.encode(clientSecret))
               .accessTokenValiditySeconds(accessTokenValiditySeconds)
               .refreshTokenValiditySeconds(refreshTokenValiditySeconds)
               .authorizedGrantTypes(authorizedGrantTypes)
               .scopes("read", "write")
               .resourceIds("api");
   }

   @Override
   public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
       endpoints
               .accessTokenConverter(accessTokenConverter())
               .userDetailsService(userService)
               .authenticationManager(authenticationManager);
   }

   @Bean
   JwtAccessTokenConverter accessTokenConverter() {
       JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
       return converter;
   }

}

The next section describes the configuration to apply to the resource server.

Setup for Resource Server

The resource server behavior is enabled by the use of @EnableResourceServer annotation and its configuration is contained in the class ResourceServerConfiguration.

The only needed configuration here is the definition of resource identification in order to match the client’s access defined in the previous class.

package net.reliqs.gleeometer.security;

import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

   @Override
   public void configure(ResourceServerSecurityConfigurer resources) {
       resources.resourceId("api");
   }

}

The last configuration element is about the definition of web application security.

Web Security Setup

Spring web security configuration is contained in the class ServerSecurityConfig, enabled by the use of @EnableWebSecurity annotation. The @EnableGlobalMethodSecurity permits to specify security on the method level. Its attribute proxyTargetClass is set in order to have this working for RestController’s methods, because controllers are usually classes, not implementing any interfaces.

It defines the following:

  • The authentication provider to use, defining the bean authenticationProvider
  • The password encoder to use, defining the bean passwordEncoder
  • The authentication manager bean
  • The security configuration for the published paths using HttpSecurity
  • Use of a custom AuthenticationEntryPoint in order to handle error messages outside of standard Spring REST error handler ResponseEntityExceptionHandler
package net.reliqs.gleeometer.security;

import net.reliqs.gleeometer.errors.CustomAccessDeniedHandler;
import net.reliqs.gleeometer.errors.CustomAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {

   private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

   private final UserDetailsService userDetailsService;

   public ServerSecurityConfig(CustomAuthenticationEntryPoint customAuthenticationEntryPoint, @Qualifier("userService")
           UserDetailsService userDetailsService) {
       this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
       this.userDetailsService = userDetailsService;
   }

   @Bean
   public DaoAuthenticationProvider authenticationProvider() {
       DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
       provider.setPasswordEncoder(passwordEncoder());
       provider.setUserDetailsService(userDetailsService);
       return provider;
   }

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

   @Bean
   @Override
   public AuthenticationManager authenticationManagerBean() throws Exception {
       return super.authenticationManagerBean();
   }

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               .and()
               .authorizeRequests()
               .antMatchers("/api/signin/**").permitAll()
               .antMatchers("/api/glee/**").hasAnyAuthority("ADMIN", "USER")
               .antMatchers("/api/users/**").hasAuthority("ADMIN")
               .antMatchers("/api/**").authenticated()
               .anyRequest().authenticated()
               .and().exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint).accessDeniedHandler(new CustomAccessDeniedHandler());
   }

}

The code extract below is about the implementation of UserDetailsService interface in order to provide the resource owner’s authentication.

package net.reliqs.gleeometer.security;

import net.reliqs.gleeometer.users.User;
import net.reliqs.gleeometer.users.UserRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;

@Service
public class UserService implements UserDetailsService {

   private final UserRepository repository;

   public UserService(UserRepository repository) {
       this.repository = repository;
   }

   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
       User user = repository.findByEmail(username).orElseThrow(() -> new RuntimeException("User not found: " + username));
       GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole().name());
       return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), Arrays.asList(authority));
   }
}

The next section is about the description of a REST controller implementation in order to see how security constraints are mapped.

REST Controller

Inside the REST controller we can find two ways to apply access control for each resource method:

  • Using an instance of OAuth2Authentication passed in by Spring as a parameter
  • Using @PreAuthorize or @PostAuthorize annotations
package net.reliqs.gleeometer.users;

import lombok.extern.slf4j.Slf4j;
import net.reliqs.gleeometer.errors.EntityNotFoundException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import javax.validation.constraints.Size;
import java.util.HashSet;

@RestController
@RequestMapping("/api/users")
@Slf4j
@Validated
class UserController {

   private final UserRepository repository;

   private final PasswordEncoder passwordEncoder;

   UserController(UserRepository repository, PasswordEncoder passwordEncoder) {
       this.repository = repository;
       this.passwordEncoder = passwordEncoder;
   }

   @GetMapping
   Page<User> all(@PageableDefault(size = Integer.MAX_VALUE) Pageable pageable, OAuth2Authentication authentication) {
       String auth = (String) authentication.getUserAuthentication().getPrincipal();
       String role = authentication.getAuthorities().iterator().next().getAuthority();
       if (role.equals(User.Role.USER.name())) {
           return repository.findAllByEmail(auth, pageable);
       }
       return repository.findAll(pageable);
   }

   @GetMapping("/search")
   Page<User> search(@RequestParam String email, Pageable pageable, OAuth2Authentication authentication) {
       String auth = (String) authentication.getUserAuthentication().getPrincipal();
       String role = authentication.getAuthorities().iterator().next().getAuthority();
       if (role.equals(User.Role.USER.name())) {
           return repository.findAllByEmailContainsAndEmail(email, auth, pageable);
       }
       return repository.findByEmailContains(email, pageable);
   }

   @GetMapping("/findByEmail")
   @PreAuthorize("!hasAuthority('USER') || (authentication.principal == #email)")
   User findByEmail(@RequestParam String email, OAuth2Authentication authentication) {
       return repository.findByEmail(email).orElseThrow(() -> new EntityNotFoundException(User.class, "email", email));
   }

   @GetMapping("/{id}")
   @PostAuthorize("!hasAuthority('USER') || (returnObject != null && returnObject.email == authentication.principal)")
   User one(@PathVariable Long id) {
       return repository.findById(id).orElseThrow(() -> new EntityNotFoundException(User.class, "id", id.toString()));
   }

   @PutMapping("/{id}")
   @PreAuthorize("!hasAuthority('USER') || (authentication.principal == @userRepository.findById(#id).orElse(new net.reliqs.gleeometer.users.User()).email)")
   void update(@PathVariable Long id, @Valid @RequestBody User res) {
       User u = repository.findById(id).orElseThrow(() -> new EntityNotFoundException(User.class, "id", id.toString()));
       res.setPassword(u.getPassword());
       res.setGlee(u.getGlee());
       repository.save(res);
   }

   @PostMapping
   @PreAuthorize("!hasAuthority('USER')")
   User create(@Valid @RequestBody User res) {
       return repository.save(res);
   }

   @DeleteMapping("/{id}")
   @PreAuthorize("!hasAuthority('USER')")
   void delete(@PathVariable Long id) {
       if (repository.existsById(id)) {
           repository.deleteById(id);
       } else {
           throw new EntityNotFoundException(User.class, "id", id.toString());
       }
   }

   @PutMapping("/{id}/changePassword")
   @PreAuthorize("!hasAuthority('USER') || (#oldPassword != null && !#oldPassword.isEmpty() && authentication.principal == @userRepository.findById(#id).orElse(new net.reliqs.gleeometer.users.User()).email)")
   void changePassword(@PathVariable Long id, @RequestParam(required = false) String oldPassword, @Valid @Size(min = 3) @RequestParam String newPassword) {
       User user = repository.findById(id).orElseThrow(() -> new EntityNotFoundException(User.class, "id", id.toString()));
       if (oldPassword == null || oldPassword.isEmpty() || passwordEncoder.matches(oldPassword, user.getPassword())) {
           user.setPassword(passwordEncoder.encode(newPassword));
           repository.save(user);
       } else {
           throw new ConstraintViolationException("old password doesn't match", new HashSet<>());
       }
   }
}

Conclusion

Spring Security and Spring Boot permit to quickly set up a complete OAuth2 authorization/authentication server in an almost declarative manner. The setup can be further shortened by configuring OAuth2 client’s properties directly from application.properties/yml file, as explained in this tutorial.

All source code is available in this GitHub repository: spring-glee-o-meter. An Angular client which consumes the published resources can be found in this GitHub repository: glee-o-meter.

Further Reading on the Toptal Blog:

Understanding the basics

  • What is OAuth2?

    OAuth2 is an authorization framework to enable a third-party application to obtain limited access to an HTTP service through the sharing of an access token. Its specification supersedes and obsoletes OAuth 1.0 protocol.

  • What is JWT?

    JWT stands for JSON Web Token, a specification for the representation of claims to be transferred between two parties. The claims are encoded as a JSON object used as the payload of an encrypted structure which enables the claims to be digitally signed or encrypted.

  • What is Spring Security?

    Spring Security is a framework focused on providing authentication and authorization to Spring-based applications.

  • What is Spring Boot?

    Spring Boot is an opinionated view of the Spring platform and third-party libraries which permits to minimize the configuration of Spring-based application while maintaining production-grade quality level.

Hire a Toptal expert on this topic.
Hire Now
Sergio Moretti

Sergio Moretti

Verified Expert in Engineering
22 Years of Experience

Castel Maggiore, Metropolitan City of Bologna, Italy

Member since December 11, 2018

About the author

Sergio has over a dozen years of experience developing enterprise-level applications with Java and RDBMS like Oracle, PostgreSQL, and MySQL.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

Expertise

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

Join the Toptal® community.