Securing a Rest API With Spring Security

Securing a Rest API With Spring Security

Most Spring Tutorials available online teach you how to secure a Rest API with Spring with examples which are far from real application problematics. You surely agree that most tutorials lack real-world use-cases.

This tutorial aims to help you secure a real-world application, not just another Hello World Example.

In this tutorial we’ll learn:

  • How to secure a Spring MVC Rest API using Spring Security,
  • Configure Spring Security with Java code (no painful XML),
  • And delegate authentication to a UserAuthenticationService with your own business logic.

I’ve spent several weeks tweaking Spring Security to come up with this simple setup. Let’s go!

Complete Source code is available on Github.

Architecture

The following Spring security setup works as following:

  • The user logs in with a POST request containing his username and password,
  • The server returns a temporary / permanent authentication token,
  • The user sends the token within each HTTP request via an HTTP header Authorization: Bearer TOKEN.

When the user logs out, the token is cleared on server-side. That’s it!

Now, let’s see different examples with variety of authentications:

  • Simple Example: authentication based on the UUID of the user,
  • JWT Example: authentication based on a JWT token.

Let’s now briefly see how the maven modules are organized. Implementing modules only depends on API modules. It’s up to the application module (like example-simple) to tie the implementations together.

Simple Example

Simple Architecture

The architecture diagram above shows how the example-simple module interacts with the other modules.

JWT Example

JWT Architecture

The main difference with the example-simple module are the dependencies on user-auth-token and token-jwt modules.

Now that we have an overview of the overall architecture, let’s dive into the code!

Maven POM

Let’s setup the root maven module with the following configuration:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <packaging>pom</packaging>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.2.RELEASE</version>
  </parent>

  <groupId>com.octoperf</groupId>
  <artifactId>securing-rest-api-spring-security</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <commons-lang.version>3.6</commons-lang.version>
    <guava.version>25.0-jre</guava.version>
    <jwt.version>0.9.0</jwt.version>
    <joda-time.version>2.9.9</joda-time.version>
    <junit.version>4.12</junit.version>
    <mockito.version>2.17.0</mockito.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>${commons-lang.version}</version>
    </dependency>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>${guava.version}</version>
    </dependency>
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>${jwt.version}</version>
    </dependency>
    <dependency>
      <groupId>joda-time</groupId>
      <artifactId>joda-time</artifactId>
      <version>${joda-time.version}</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>${mockito.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava-testlib</artifactId>
      <version>${guava.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
          <!-- mandatory to compile project with maven 3.3.9, might be removed with latest version -->
          <useIncrementalCompilation>false</useIncrementalCompilation>
          <source>1.8</source>
          <target>1.8</target>
          <optimize>true</optimize>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

In this example, we’re going to use Spring Boot 2 to quickly setup a web application using Spring MVC and Spring Security.

Common Configuration

User Management

In this section, i’m going to cover the implementation of the code responsible of logging in and out users.

user-entity

The user-entity module contains User class which represents a single user:


package com.octoperf.user.entity;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

import static java.util.Objects.requireNonNull;

@Value
@Builder
public class User implements UserDetails {
  private static final long serialVersionUID = 2396654715019746670L;

  String id;
  String username;
  String password;

  @JsonCreator
  User(@JsonProperty("id") final String id,
       @JsonProperty("username") final String username,
       @JsonProperty("password") final String password) {
    super();
    this.id = requireNonNull(id);
    this.username = requireNonNull(username);
    this.password = requireNonNull(password);
  }

  @JsonIgnore
  @Override
  public Collection<GrantedAuthority> getAuthorities() {
    return new ArrayList<>();
  }

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

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

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

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

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

}

To match Spring Security API, the User class implements UserDetails. This way, our custom User bean seamlessly integrates into Spring Security.

user-crud-api

The User crud API is responsible of storing users somewhere.

package com.octoperf.user.crud.api;

import com.octoperf.user.entity.User;

import java.util.Optional;

/**
 * User security operations like login and logout, and CRUD operations on {@link User}.
 * 
 * @author jerome
 *
 */
public interface UserCrudService {

  User save(User user);

  Optional<User> find(String id);

  Optional<User> findByUsername(String username);
}

The unique implementation is InMemoryUsers located in module user-crud-in-memory:


package com.octoperf.user.crud.in.memory;

import com.octoperf.user.crud.api.UserCrudService;
import com.octoperf.user.entity.User;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static java.util.Optional.ofNullable;

@Service
final class InMemoryUsers implements UserCrudService {

  Map<String, User> users = new HashMap<>();

  @Override
  public User save(final User user) {
    return users.put(user.getId(), user);
  }

  @Override
  public Optional<User> find(final String id) {
    return ofNullable(users.get(id));
  }

  @Override
  public Optional<User> findByUsername(final String username) {
    return users
      .values()
      .stream()
      .filter(u -> Objects.equals(username, u.getUsername()))
      .findFirst();
  }
}


As you can see, users are stored in Map<String, User> in memory. This is purely for demonstration purpose. Of course, a real application would be based on a UserCrudService storing users in a real database.

user-auth-api

The user-auth-api contains UserAuthenticationService is responsible of logging in and out the users, as well as deliver the authentication tokens.


package com.octoperf.auth.api;

import com.octoperf.user.entity.User;

import java.util.Optional;

public interface UserAuthenticationService {

  /**
   * Logs in with the given {@code username} and {@code password}.
   *
   * @param username
   * @param password
   * @return an {@link Optional} of a user when login succeeds
   */
  Optional<String> login(String username, String password);

  /**
   * Finds a user by its dao-key.
   *
   * @param token user dao key
   * @return
   */
  Optional<User> findByToken(String token);

  /**
   * Logs out the given input {@code user}.
   *
   * @param user the user to logout
   */
  void logout(User user);
}


In this tutorial, i’m going to use 2 different implementations depending on the example we’ll see.

Spring Security Config

The whole Spring Security configuration is stored in security-config module.

Redirect Strategy

As we’re securing a REST API, in case of authentication failure, the server should not redirect to any error page. The server will simply return an HTTP 401 (Unauthorized). Here is the NoRedirectStrategy located in com.octoperf.security package:


package com.octoperf.security.config;

import org.springframework.security.web.RedirectStrategy;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

class NoRedirectStrategy implements RedirectStrategy {

  @Override
  public void sendRedirect(final HttpServletRequest request, final HttpServletResponse response, final String url) throws IOException {
      // No redirect is required with pure REST
  }
}

Nothing fancy here, the purpose is to keep things simple.

Token Authentication Provider

The TokenAuthenticationProvider is responsible of finding the user by it’s authentication token.

package com.octoperf.security.config;

import com.octoperf.auth.api.UserAuthenticationService;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.Optional;

import static lombok.AccessLevel.PACKAGE;
import static lombok.AccessLevel.PRIVATE;

@Component
@AllArgsConstructor(access = PACKAGE)
@FieldDefaults(level = PRIVATE, makeFinal = true)
final class TokenAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
  @NonNull
  UserAuthenticationService auth;

  @Override
  protected void additionalAuthenticationChecks(final UserDetails d, final UsernamePasswordAuthenticationToken auth) {
    // Nothing to do
  }

  @Override
  protected UserDetails retrieveUser(final String username, final UsernamePasswordAuthenticationToken authentication) {
    final Object token = authentication.getCredentials();
    return Optional
      .ofNullable(token)
      .map(String::valueOf)
      .flatMap(auth::findByToken)
      .orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
  }
}

The TokenAuthenticationProvider delegates to the UserAuthenticationService we have seen in the previous section.

TokenAuthenticationFilter

The TokenAuthenticationFilter is responsible of extracting the authentication token from the request headers. It takes the Authorization header value and attempts to extract the token from it.

package com.octoperf.security.config;

import lombok.experimental.FieldDefaults;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static java.util.Optional.ofNullable;
import static lombok.AccessLevel.PRIVATE;
import static org.apache.commons.lang3.StringUtils.removeStart;

@FieldDefaults(level = PRIVATE, makeFinal = true)
final class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  private static final String BEARER = "Bearer";

  TokenAuthenticationFilter(final RequestMatcher requiresAuth) {
    super(requiresAuth);
  }

  @Override
  public Authentication attemptAuthentication(
    final HttpServletRequest request,
    final HttpServletResponse response) {
    final String param = ofNullable(request.getHeader(AUTHORIZATION))
      .orElse(request.getParameter("t"));

    final String token = ofNullable(param)
      .map(value -> removeStart(value, BEARER))
      .map(String::trim)
      .orElseThrow(() -> new BadCredentialsException("Missing Authentication Token"));

    final Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
    return getAuthenticationManager().authenticate(auth);
  }

  @Override
  protected void successfulAuthentication(
    final HttpServletRequest request,
    final HttpServletResponse response,
    final FilterChain chain,
    final Authentication authResult) throws IOException, ServletException {
    super.successfulAuthentication(request, response, chain, authResult);
    chain.doFilter(request, response);
  }
}

Again, nothing fancy here. The code is pretty straight forward! Authentication is then delegated to the AuthenticationManager. The filter is only enabled for a given set of urls. We are going to see in the next coming sections how this filter is configured.

SecurityConfig

It’s time to configure Spring Security with all the services we defined above:

package com.octoperf.security.config;

import lombok.experimental.FieldDefaults;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
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.method.configuration.EnableGlobalMethodSecurity;
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.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import static java.util.Objects.requireNonNull;
import static lombok.AccessLevel.PRIVATE;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
@FieldDefaults(level = PRIVATE, makeFinal = true)
class SecurityConfig extends WebSecurityConfigurerAdapter {
  private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
    new AntPathRequestMatcher("/public/**")
  );
  private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);

  TokenAuthenticationProvider provider;

  SecurityConfig(final TokenAuthenticationProvider provider) {
    super();
    this.provider = requireNonNull(provider);
  }

  @Override
  protected void configure(final AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(provider);
  }

  @Override
  public void configure(final WebSecurity web) {
    web.ignoring().requestMatchers(PUBLIC_URLS);
  }

  @Override
  protected void configure(final HttpSecurity http) throws Exception {
    http
      .sessionManagement()
      .sessionCreationPolicy(STATELESS)
      .and()
      .exceptionHandling()
      // this entry point handles when you request a protected page and you are not yet
      // authenticated
      .defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
      .and()
      .authenticationProvider(provider)
      .addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
      .authorizeRequests()
      .anyRequest()
      .authenticated()
      .and()
      .csrf().disable()
      .formLogin().disable()
      .httpBasic().disable()
      .logout().disable();
  }

  @Bean
  TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
    final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
    filter.setAuthenticationManager(authenticationManager());
    filter.setAuthenticationSuccessHandler(successHandler());
    return filter;
  }

  @Bean
  SimpleUrlAuthenticationSuccessHandler successHandler() {
    final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
    successHandler.setRedirectStrategy(new NoRedirectStrategy());
    return successHandler;
  }

  /**
   * Disable Spring boot automatic filter registration.
   */
  @Bean
  FilterRegistrationBean disableAutoRegistration(final TokenAuthenticationFilter filter) {
    final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
  }

  @Bean
  AuthenticationEntryPoint forbiddenEntryPoint() {
    return new HttpStatusEntryPoint(FORBIDDEN);
  }
}

Let’s review how Spring Security is configured here:

  • URLs starting with /public/** are excluded from security, which means any url starting with /public will not be secured,
  • The TokenAuthenticationFilter is registered within the Spring Security Filter Chain very early. We want it to catch any authentication token passing by,
  • Most other login methods like formLogin or httpBasic have been disabled as we’re not willing to use them here (we want to use our own system),
  • Some boiler-plate code to disable automatic filter registration, related to Spring Boot.

As you can see, everything is tied together in a Java configuration which is almost less than 100 lines everything combined!

Now, we’re going to setup a few Spring MVC RestController to be able to login and logout.

Spring MVC Controllers

Those controllers are shared by all the examples we’ll see below.

PublicUsersController

The PublicUsersController allows a user to login into the application:

package com.octoperf.user.controller;

import com.octoperf.auth.api.UserAuthenticationService;
import com.octoperf.user.crud.api.UserCrudService;
import com.octoperf.user.entity.User;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

import static lombok.AccessLevel.PACKAGE;
import static lombok.AccessLevel.PRIVATE;

@RestController
@RequestMapping("/public/users")
@FieldDefaults(level = PRIVATE, makeFinal = true)
@AllArgsConstructor(access = PACKAGE)
final class PublicUsersController {
  @NonNull
  UserAuthenticationService authentication;
  @NonNull
  UserCrudService users;

  @PostMapping("/register")
  String register(
    @RequestParam("username") final String username,
    @RequestParam("password") final String password) {
    users
      .save(
        User
          .builder()
          .id(username)
          .username(username)
          .password(password)
          .build()
      );

    return login(username, password);
  }

  @PostMapping("/login")
  String login(
    @RequestParam("username") final String username,
    @RequestParam("password") final String password) {
    return authentication
      .login(username, password)
      .orElseThrow(() -> new RuntimeException("invalid login and/or password"));
  }
}



It offers 2 different Endpoints:

  • String register(@RequestParam("username") final String username, @RequestParam("password") final String password): Register a new user and return an authentication token,
  • String login(@RequestParam("username") final String username, @RequestParam("password") final String password): login an existing user and return an authentication token (if any user found with matching password).

The authentication is delegated to UserAuthenticationService implementation. UserCrudService is responsible of storing the user.

SecuredUsersController

The SecuredUsersController is by definition permitting the user to perform operations only when logged in:

  • Get the current user bean,
  • Logout from the application.

Here is the code:

package com.octoperf.user.controller;

import com.octoperf.auth.api.UserAuthenticationService;
import com.octoperf.user.entity.User;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import static lombok.AccessLevel.PACKAGE;
import static lombok.AccessLevel.PRIVATE;

@RestController
@RequestMapping("/users")
@FieldDefaults(level = PRIVATE, makeFinal = true)
@AllArgsConstructor(access = PACKAGE)
final class SecuredUsersController {
  @NonNull
  UserAuthenticationService authentication;

  @GetMapping("/current")
  User getCurrent(@AuthenticationPrincipal final User user) {
    return user;
  }

  @GetMapping("/logout")
  boolean logout(@AuthenticationPrincipal final User user) {
    authentication.logout(user);
    return true;
  }
}

Again, nothing difficult here! It’s time now to test the application. To do so, we need to create a Spring Boot bootstrap class.

Application Bootstrap

The Application class placed in root package com.octoperf in maven module bootstrap is responsible for bootstrapping the application via Spring Boot:

package com.octoperf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args); // NOSONAR
  }
}

We’re going to run this application like a simple Java main to launch the server. Finally, the server runs on port 8080 by default. As I already have a server running on this port on my machine. As a result, I configured Spring Boot to run on port 8081 via an application.yml located in bootstrap module too:


server.port: 8081

Simple Example

user-auth-uuid

This module contains a UserAuthenticationService which is based on a simple random UUID.


package com.octoperf.user.auth.map;

import com.octoperf.auth.api.UserAuthenticationService;
import com.octoperf.user.crud.api.UserCrudService;
import com.octoperf.user.entity.User;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.UUID;

import static lombok.AccessLevel.PACKAGE;
import static lombok.AccessLevel.PRIVATE;

@Service
@AllArgsConstructor(access = PACKAGE)
@FieldDefaults(level = PRIVATE, makeFinal = true)
final class UUIDAuthenticationService implements UserAuthenticationService {
  @NonNull
  UserCrudService users;

  @Override
  public Optional<String> login(final String username, final String password) {
    final String uuid = UUID.randomUUID().toString();
    final User user = User
      .builder()
      .id(uuid)
      .username(username)
      .password(password)
      .build();

    users.save(user);
    return Optional.of(uuid);
  }

  @Override
  public Optional<User> findByToken(final String token) {
    return users.find(token);
  }

  @Override
  public void logout(final User user) {

  }
}


The service logs in any user. I’ve said it, it’s pretty simple! You can plug here your own authentication logic instead of this dummy one. It’s up to you to adapt the code to your own needs.

Configuration

The example-simple module is an example application which has the following configuration:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <packaging>jar</packaging>
  <parent>
    <groupId>com.octoperf</groupId>
    <artifactId>securing-rest-api-spring-security</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <groupId>com.octoperf</groupId>
  <artifactId>example-simple</artifactId>
  <version>1.0-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>user-auth-uuid</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>user-controller</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>user-crud-in-memory</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>bootstrap</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>security-config</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

It features a simple example based on the UUID authentication token: the id of the user is used as authentication token.

Intellij Simple Example

Create a launcher (within Intellij) with following configuration:

  • Main Class: com.octoperf.Application,
  • Working Directory: $MODULE_DIR$,
  • Use classpath of module: example-simple.

Then run it. The application should be running within a few seconds:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.2.RELEASE)

2018-05-30 10:49:11.705  INFO 23199 --- [           main] com.octoperf.Application                 : Starting Application on ubuntu-Aspire-V3-772 with PID 23199 (/home/ubuntu/git/securing-rest-api-spring-security/bootstrap/target/classes started by ubuntu in /home/ubuntu/git/securing-rest-api-spring-security/example-simple)
2018-05-30 10:49:11.709  INFO 23199 --- [           main] com.octoperf.Application                 : No active profile set, falling back to default profiles: default
2018-05-30 10:49:11.777  INFO 23199 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6e4784bc: startup date [Wed May 30 10:49:11 CEST 2018]; root of context hierarchy
2018-05-30 10:49:13.419  INFO 23199 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8081 (http)
2018-05-30 10:49:13.445  INFO 23199 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-05-30 10:49:13.446  INFO 23199 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.31
2018-05-30 10:49:13.456  INFO 23199 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib]
2018-05-30 10:49:13.547  INFO 23199 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-05-30 10:49:13.547  INFO 23199 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1774 ms
2018-05-30 10:49:13.861  INFO 23199 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-05-30 10:49:13.862  INFO 23199 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-05-30 10:49:13.862  INFO 23199 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-05-30 10:49:13.862  INFO 23199 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-05-30 10:49:13.862  INFO 23199 --- [ost-startStop-1] .s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
2018-05-30 10:49:13.863  INFO 23199 --- [ost-startStop-1] o.s.boot.web.servlet.RegistrationBean    : Filter tokenAuthenticationFilter was not registered (disabled)
2018-05-30 10:49:13.863  INFO 23199 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2018-05-30 10:49:14.049  INFO 23199 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/public/**']]], []
2018-05-30 10:49:14.130  INFO 23199 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1da6ee17, org.springframework.security.web.context.SecurityContextPersistenceFilter@181d7f28, org.springframework.security.web.header.HeaderWriterFilter@4010d494, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2fb69ff6, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@1929425f, com.octoperf.security.config.TokenAuthenticationFilter@18df85a7, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@78d39a69, org.springframework.security.web.session.SessionManagementFilter@4943defe, org.springframework.security.web.access.ExceptionTranslationFilter@68ace111, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@64d43929]
2018-05-30 10:49:14.338  INFO 23199 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-30 10:49:14.494  INFO 23199 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6e4784bc: startup date [Wed May 30 10:49:11 CEST 2018]; root of context hierarchy
2018-05-30 10:49:14.566  INFO 23199 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/public/users/login],methods=[POST]}" onto java.lang.String com.octoperf.user.controller.PublicUsersController.login(java.lang.String,java.lang.String)
2018-05-30 10:49:14.567  INFO 23199 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/public/users/register],methods=[POST]}" onto java.lang.String com.octoperf.user.controller.PublicUsersController.register(java.lang.String,java.lang.String)
2018-05-30 10:49:14.570  INFO 23199 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/logout],methods=[GET]}" onto boolean com.octoperf.user.controller.SecuredUsersController.logout(com.octoperf.user.entity.User)
2018-05-30 10:49:14.570  INFO 23199 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/current],methods=[GET]}" onto com.octoperf.user.entity.User com.octoperf.user.controller.SecuredUsersController.getCurrent(com.octoperf.user.entity.User)
2018-05-30 10:49:14.573  INFO 23199 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-05-30 10:49:14.573  INFO 23199 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-05-30 10:49:14.602  INFO 23199 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-30 10:49:14.602  INFO 23199 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-30 10:49:14.747  INFO 23199 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-05-30 10:49:14.775  INFO 23199 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ''
2018-05-30 10:49:14.779  INFO 23199 --- [           main] com.octoperf.Application                 : Started Application in 3.608 seconds (JVM running for 4.099)

Great! The server is up and running, ready to be used. Let’s now perform some requests using curl.

Testing the Application

First, let’s register on the REST API:


ubuntu@ubuntu-Aspire-V3-772:~$ curl -XPOST -d 'username=john&password=smith' http://localhost:8081/public/users/register
b856850e-1ad4-456d-b5ca-1c2bfc355e5

Then we can also login with this username and password:


ubuntu@ubuntu-Aspire-V3-772:~$ curl -XPOST -d 'username=john&password=smith' http://localhost:8081/public/users/login
b856850e-1ad4-456d-b5ca-1c2bfc355e5

By sending an url-encoded form post request to the endpoint, it returns as expected a random UUID. Now, let’s use the UUID in a subsequent request to retrieve the current user:


ubuntu@ubuntu-Aspire-V3-772:~$ curl -H 'Authorization: Bearer b856850e-1ad4-456d-b5ca-1c2bfc355e5e' http://localhost:8081/users/current
{"id":"b856850e-1ad4-456d-b5ca-1c2bfc355e5e","username":"john","enabled":true}

Nice! We’re logged into the system and we could retrieve the current user in Json format. By default, Spring Boot uses Jackson Json API to serialize beans into Json.

Let’s now logout from the system:


ubuntu@ubuntu-Aspire-V3-772:~$ curl -H 'Authorization: Bearer b856850e-1ad4-456d-b5ca-1c2bfc355e5e' http://localhost:8081/users/logout
true

If we try to get the current user again with the same authentication token, we should receive an error:


ubuntu@ubuntu-Aspire-V3-772:~$ curl -H 'Authorization: Bearer b856850e-1ad4-456d-b5ca-1c2bfc355e5e' http://localhost:8081/users/current
{"timestamp":1516184750678,"status":401,"error":"Unauthorized","message":"Authentication Failed: Bad credentials","path":"/users/current"}

As expected, the server denied the access to the secured resource because the authentication token has been previously revoked.

JWT Example

What if you want to have a token that expires after some time (like 24h for example)? We can leverage JWT tokens for that. JWT Tokens are typically formatted as following:

xxxx.yyyy.zzzz

The token is generated and signed by the server. It’s possible to decode it easily, but it’s not possible to generate a new one unless you know the server secret key. That very convenient!

How does it work:

  • First, we authenticate with username and password,
  • The server responds with a signed JWT Token which contains the user id,
  • Susbequent requests are sent with Authorization: Bearer TOKEN,
  • On each request, the server verify the JWT token is properly signed by himself and extracts the user id to identify the user.

token-api

The token-api module contains the TokenService API:


package com.octoperf.token.api;

import java.util.Map;

/**
 * Creates and validates credentials.
 */
public interface TokenService {

  String permanent(Map<String, String> attributes);

  String expiring(Map<String, String> attributes);

  /**
   * Checks the validity of the given credentials.
   *
   * @param token
   * @return attributes if verified
   */
  Map<String, String> untrusted(String token);

  /**
   * Checks the validity of the given credentials.
   *
   * @param token
   * @return attributes if verified
   */
  Map<String, String> verify(String token);
}

The token service is responsible of generating and validating JWT tokens. Let’s see the implementation now.

token-jwt

Now let’s implement the JWTTokenService:


package com.octoperf.token.jwt;

import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.octoperf.date.service.DateService;
import com.octoperf.token.api.TokenService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.compression.GzipCompressionCodec;
import lombok.experimental.FieldDefaults;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;

import static io.jsonwebtoken.SignatureAlgorithm.HS256;
import static io.jsonwebtoken.impl.TextCodec.BASE64;
import static java.util.Objects.requireNonNull;
import static lombok.AccessLevel.PRIVATE;
import static org.apache.commons.lang3.StringUtils.substringBeforeLast;

@Service
@FieldDefaults(level = PRIVATE, makeFinal = true)
final class JWTTokenService implements Clock, TokenService {
  private static final String DOT = ".";
  private static final GzipCompressionCodec COMPRESSION_CODEC = new GzipCompressionCodec();

  DateService dates;
  String issuer;
  int expirationSec;
  int clockSkewSec;
  String secretKey;

  JWTTokenService(final DateService dates,
                  @Value("${jwt.issuer:octoperf}") final String issuer,
                  @Value("${jwt.expiration-sec:86400}") final int expirationSec,
                  @Value("${jwt.clock-skew-sec:300}") final int clockSkewSec,
                  @Value("${jwt.secret:secret}") final String secret) {
    super();
    this.dates = requireNonNull(dates);
    this.issuer = requireNonNull(issuer);
    this.expirationSec = requireNonNull(expirationSec);
    this.clockSkewSec = requireNonNull(clockSkewSec);
    this.secretKey = BASE64.encode(requireNonNull(secret));
  }

  @Override
  public String permanent(final Map<String, String> attributes) {
    return newToken(attributes, 0);
  }

  @Override
  public String expiring(final Map<String, String> attributes) {
    return newToken(attributes, expirationSec);
  }

  private String newToken(final Map<String, String> attributes, final int expiresInSec) {
    final DateTime now = dates.now();
    final Claims claims = Jwts
      .claims()
      .setIssuer(issuer)
      .setIssuedAt(now.toDate());

    if (expiresInSec > 0) {
      final DateTime expiresAt = now.plusSeconds(expiresInSec);
      claims.setExpiration(expiresAt.toDate());
    }
    claims.putAll(attributes);

    return Jwts
      .builder()
      .setClaims(claims)
      .signWith(HS256, secretKey)
      .compressWith(COMPRESSION_CODEC)
      .compact();
  }

  @Override
  public Map<String, String> verify(final String token) {
    final JwtParser parser = Jwts
      .parser()
      .requireIssuer(issuer)
      .setClock(this)
      .setAllowedClockSkewSeconds(clockSkewSec)
      .setSigningKey(secretKey);
    return parseClaims(() -> parser.parseClaimsJws(token).getBody());
  }

  @Override
  public Map<String, String> untrusted(final String token) {
    final JwtParser parser = Jwts
      .parser()
      .requireIssuer(issuer)
      .setClock(this)
      .setAllowedClockSkewSeconds(clockSkewSec);

    // See: https://github.com/jwtk/jjwt/issues/135
    final String withoutSignature = substringBeforeLast(token, DOT) + DOT;
    return parseClaims(() -> parser.parseClaimsJwt(withoutSignature).getBody());
  }

  private static Map<String, String> parseClaims(final Supplier<Claims> toClaims) {
    try {
      final Claims claims = toClaims.get();
      final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
      for (final Map.Entry<String, Object> e: claims.entrySet()) {
        builder.put(e.getKey(), String.valueOf(e.getValue()));
      }
      return builder.build();
    } catch (final IllegalArgumentException | JwtException e) {
      return ImmutableMap.of();
    }
  }

  @Override
  public Date now() {
    final DateTime now = dates.now();
    return now.toDate();
  }
}

I’m using jjwt library for that:

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>${jwt.version}</version>
</dependency>

Nothing fancy here, i’m just using JJWT API as explained on their Github Page. Now, it’s time to use this TokenService in our authentication flow!

user-auth-token

The user-auth-token module is responsible of authenticating a user with the TokenService. It contains the TokenAuthenticationService:

package com.octoperf.user.auth.crud;

import com.google.common.collect.ImmutableMap;
import com.octoperf.auth.api.UserAuthenticationService;
import com.octoperf.token.api.TokenService;
import com.octoperf.user.crud.api.UserCrudService;
import com.octoperf.user.entity.User;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.Optional;

import static lombok.AccessLevel.PACKAGE;
import static lombok.AccessLevel.PRIVATE;

@Service
@AllArgsConstructor(access = PACKAGE)
@FieldDefaults(level = PRIVATE, makeFinal = true)
final class TokenAuthenticationService implements UserAuthenticationService {
  @NonNull
  TokenService tokens;
  @NonNull
  UserCrudService users;

  @Override
  public Optional<String> login(final String username, final String password) {
    return users
      .findByUsername(username)
      .filter(user -> Objects.equals(password, user.getPassword()))
      .map(user -> tokens.expiring(ImmutableMap.of("username", username)));
  }

  @Override
  public Optional<User> findByToken(final String token) {
    return Optional
      .of(tokens.verify(token))
      .map(map -> map.get("username"))
      .flatMap(users::findByUsername);
  }

  @Override
  public void logout(final User user) {
    // Nothing to doy
  }
}

As you can see, when a user logs in, we return a token which contains the user username. We’ll use that later to find the user again when authenticating him.

The great thing is The entire token logic is encapsulated within the UserAuthenticationService.

Configuration

The example-jwt module glues everything together:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.octoperf</groupId>
    <artifactId>securing-rest-api-spring-security</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <groupId>com.octoperf</groupId>
  <artifactId>example-jwt</artifactId>
  <version>1.0-SNAPSHOT</version>


  <dependencies>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>user-entity</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>user-controller</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>token-jwt</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>user-crud-in-memory</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>user-auth-token</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>bootstrap</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.octoperf</groupId>
      <artifactId>security-config</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

Like previously, create an Intellij launcher with the following configuration:

  • Main Class: com.octoperf.Application,
  • Working Directory: $MODULE_DIR$,
  • Use classpath of module: example-jwt.

Then run it. The application should be running within a few seconds:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.2.RELEASE)

2018-05-30 11:01:55.618  INFO 27723 --- [           main] com.octoperf.Application                 : Starting Application on ubuntu-Aspire-V3-772 with PID 27723 (/home/ubuntu/git/securing-rest-api-spring-security/bootstrap/target/classes started by ubuntu in /home/ubuntu/git/securing-rest-api-spring-security/example-jwt)
2018-05-30 11:01:55.624  INFO 27723 --- [           main] com.octoperf.Application                 : No active profile set, falling back to default profiles: default
2018-05-30 11:01:55.702  INFO 27723 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6e4784bc: startup date [Wed May 30 11:01:55 CEST 2018]; root of context hierarchy
2018-05-30 11:01:57.039  INFO 27723 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8081 (http)
2018-05-30 11:01:57.072  INFO 27723 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-05-30 11:01:57.072  INFO 27723 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.31
2018-05-30 11:01:57.083  INFO 27723 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib]
2018-05-30 11:01:57.161  INFO 27723 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-05-30 11:01:57.161  INFO 27723 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1462 ms
2018-05-30 11:01:57.566  INFO 27723 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-05-30 11:01:57.566  INFO 27723 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-05-30 11:01:57.566  INFO 27723 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-05-30 11:01:57.567  INFO 27723 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-05-30 11:01:57.567  INFO 27723 --- [ost-startStop-1] .s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
2018-05-30 11:01:57.567  INFO 27723 --- [ost-startStop-1] o.s.boot.web.servlet.RegistrationBean    : Filter tokenAuthenticationFilter was not registered (disabled)
2018-05-30 11:01:57.567  INFO 27723 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2018-05-30 11:01:57.745  INFO 27723 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/public/**']]], []
2018-05-30 11:01:57.832  INFO 27723 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@49d98dc5, org.springframework.security.web.context.SecurityContextPersistenceFilter@456abb66, org.springframework.security.web.header.HeaderWriterFilter@55b5e331, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da10b5b, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@595f4da5, com.octoperf.security.config.TokenAuthenticationFilter@32dd2877, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@2c30b71f, org.springframework.security.web.session.SessionManagementFilter@15fc442, org.springframework.security.web.access.ExceptionTranslationFilter@20921b9b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@55f45b92]
2018-05-30 11:01:57.948  INFO 27723 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-30 11:01:58.242  INFO 27723 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6e4784bc: startup date [Wed May 30 09:01:55 UTC 2018]; root of context hierarchy
2018-05-30 11:01:58.342  INFO 27723 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/public/users/login],methods=[POST]}" onto java.lang.String com.octoperf.user.controller.PublicUsersController.login(java.lang.String,java.lang.String)
2018-05-30 11:01:58.343  INFO 27723 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/public/users/register],methods=[POST]}" onto java.lang.String com.octoperf.user.controller.PublicUsersController.register(java.lang.String,java.lang.String)
2018-05-30 11:01:58.347  INFO 27723 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/logout],methods=[GET]}" onto boolean com.octoperf.user.controller.SecuredUsersController.logout(com.octoperf.user.entity.User)
2018-05-30 11:01:58.347  INFO 27723 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/current],methods=[GET]}" onto com.octoperf.user.entity.User com.octoperf.user.controller.SecuredUsersController.getCurrent(com.octoperf.user.entity.User)
2018-05-30 11:01:58.351  INFO 27723 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-05-30 11:01:58.352  INFO 27723 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-05-30 11:01:58.396  INFO 27723 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-30 11:01:58.397  INFO 27723 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-30 11:01:58.588  INFO 27723 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-05-30 11:01:58.628  INFO 27723 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ''
2018-05-30 11:01:58.631  INFO 27723 --- [           main] com.octoperf.Application                 : Started Application in 3.606 seconds (JVM running for 4.041)

Testing The Application

First, let’s register on the REST API:


ubuntu@ubuntu-Aspire-V3-772:~$ curl -XPOST -d 'username=john&password=smith' http://localhost:8081/public/users/register
eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWyiwuVrJSyk8uyS9ILUpT0lHKTCxRsjI0NTI3Mzc0NDXUUUqtKIAImJuam4IESotTi_ISc1OB-rLyM_KUagHqL4qjRgAAAA.jsmDSIYGoG-EKZr-Yw5G2k3c6Ano69A0nAncA8dnHBw

Here we got a JWT Token now! Then we can also login with this username and password:


ubuntu@ubuntu-Aspire-V3-772:~$ curl -XPOST -d 'username=john&password=smith' http://localhost:8081/public/users/login
eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWyiwuVrJSyk8uyS9ILUpT0lHKTCxRsjI0NTI3Mzc0tLDUUUqtKIAImJuam4IESotTi_ISc1OB-rLyM_KUagFKIH8rRgAAAA.E4bC_Vrvm7rD2Ms6KWHwotZUNTbFB7TK_3Wnc1LQpE8

By sending an url-encoded form post request to the endpoint, it returns as expected a random UUID. Now, let’s use the UUID in a subsequent request to retrieve the current user:

ubuntu@ubuntu-Aspire-V3-772:~$ -772:~$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAifQ...' http://localhost:8081/users/current
{"id":"john","username":"john","enabled":true}

Nice! We’re logged into the system and we could retrieve the current user in Json format. By default, Spring Boot uses Jackson Json API to serialize beans into Json.

Let’s now logout from the system:


ubuntu@ubuntu-Aspire-V3-772:~$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAifQ...' http://localhost:8081/users/logout
true

If we try to get the current user again with the same authentication token, we should receive an error:


ubuntu@ubuntu-Aspire-V3-772:~$ curl -H 'Authorization: Bearer ...' http://localhost:8081/users/current
{"timestamp":1516184750678,"status":401,"error":"Unauthorized","message":"Authentication Failed: Bad credentials","path":"/users/current"}

As expected, the server denied the access to the secured resource because the authentication token has been previously revoked.

Final Words

I hope this ready to use skeleton security layer will enable you to build a secure Rest API using Spring Security. It took a while to figure out how Spring Security works, and how to create this configuration.

As you can see, the system is designed in a way it’s easy to replace the authentication logic with another.

We thought it would be a good idea to share this tutorial to help you avoid spending weeks messing around with Spring Security (as we did).

By - CTO.
Tags: Java Spring Rest Api Security

Comments

Abhishek  

Please provide the link for downloading source code. Is there a difference in securing Rest Apis and JSPs?
Reply

Jerome
In reply to Abhishek
 

Hi Abhisek, You can download the complete source code on Github project. There is no real difference in securing Rest Api endpoints and JSPs. Main difference will be that Rest APIs are mostly @RestController (assuming responses have a body like a json doc) and JSPs are usually @Controller annotated.

Also Check @Pre and @Post annotations on Spring’s website to fine-tune security per controller’s method.

Shashank Rajput  

This was very helpful for me in clearing doubts. I spent two weeks to understand the flow of spring security to create a login system using spring boot at backend and angular at frontend. This blog helped me a lot and solved my problem.
Reply

Jerome
In reply to Shashank Rajput
 

Glad you found this article helpful! Cheers.

Thomas  

I was using your tutorial to secure my app. But now i have some problems. When i try to access to public endpoints my app returns 404 Not Found. Any idea where I made mistake ?
Reply

Jerome
In reply to Thomas
 

Hi Thomas,

Please make sure your application modules containing your controllers are correctly declared as dependencies. HTTP 404 Not Found typically occur in that case. Otherwise try to ask for help on communities like StackOverFlow.

vikas  

Very nice articlet, thanks for the reply I have just implemented the security working via this article
Reply

Jerome
In reply to vikas
 

Hi Vikas, I’m glad you liked our article and could implement it in your own web application! Best regards.

Rajib  

I was trying to use your code in Spring-Web application and not in Spring-Boot. And it did not work. Can you suggest changes to make this example work in Spring-Web?
Reply

Jerome
In reply to Rajib
 

Hi Rajib,

Unfortunately without more information about your issue, It’s difficult to find out what’s wrong within your application. Spring Security Documentation might help you.

Abdul Ahad  

Hi Jerome,

Can you please do a tutorial on how to create token with specific life span like token is only valid for 24 hours etc.

Reply

Jerome
In reply to Abdul Ahad
 

Hi Abdul,

That’s a good idea! Especially because we already have implemented such kind of logic within our server using Json Web Tokens. Come back in a few days, and you should see this tutorial online here :)

Cheers

Rajib  

Hi Jerome, I got it to work by adding an an empty class: java public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer { } But I have another question - In this example, we are storing the users in a Map in class SimpleAuthenticationService. I am also planning to do the same, but how will I make it work in a clustered / distributed environment? By any chance, can you modify SimpleAuthenticationService to support clustering? I mean, if I deploy this application across clusters (for example multiple WebSphere nodes), how will we share the users map across clusters? Any reference implementation will be of great help.
Reply

Jerome
In reply to Rajib
 

Hi Rajib,

Thanks for the insight. Most web application store users in a database (like MySQL, Elasticsearch etc.). Therefore, in a clustered / distributed environment, the database is typically accessible from all the web servers. Also, when using JWT tokens with the same secret key the authentication should work on any server. It’s the perfect stateless authentication!

If still want to store users in a Map<String, User>, you can use Hazelcast. It’s an in-memory data grid which has distributed Map implementations. It’s pretty easy to use! We use it for a distributed event-bus in OctoPerf Rest Server.

Rajib  

Hi Jerome, I am trying to use Hazelcast now, but having a hard time integrating the same with Spring Web. Please note, I am trying to integrate Hazelcast with Spring Web (not Spring Boot) and I am looking for java annotation based approach. But I am not able to get any convincing article on this. Any help in this respect will be greatly appreciated.
Reply

Jerome
In reply to Rajib
 

Hi Rajib,

I guess you are trying to setup Spring sessions with Hazelcast, which is quite difficult. Is it what you are trying to do? It really depends on what you want to do.

Hazelcast in itself is very simple to configure and create via annotations based spring configuration (using @Configuration and @Bean). Here is an extract of our code which uses Hazelcast clustering too:

package com.octoperf.commons.cluster.hazelcast;

import ...;

@Configuration
class HazelcastConfig {
  private static final Splitter COMA = Splitter.on(',').trimResults();

  @Bean
  Config config(
    final MembershipListener listener,
    @Value("${clustering.hazelcast.members:127.0.0.1}") final String members) throws UnknownHostException {
    final Config config = new Config();

    final NetworkConfig network = config.getNetworkConfig();
    
    final JoinConfig join = network.getJoin();
    final MulticastConfig multicast = join.getMulticastConfig();
    multicast.setEnabled(false);
    
    final TcpIpConfig tcpIp = join.getTcpIpConfig();

    tcpIp.setEnabled(true);
    for(final String member : COMA.splitToList(members)) {
      final InetAddress[] addresses = MoreObjects.firstNonNull(
        InetAddress.getAllByName(member),
        new InetAddress[0]);
      for (final InetAddress addr : addresses) {
        final String hostAddress = addr.getHostAddress();
        tcpIp.addMember(hostAddress);
        log.info("[Hazelcast] New Member: " + hostAddress);
      }
    }

    return config.addListenerConfig(new ListenerConfig(listener));
  }
}

But we use Spring Boot, which automatically creates an instance of HazelcastInstance when a Config bean is defined. Otherwise, you may want to create it yourself by adding to the HazelcastConfig

  @Bean
  HazelcastInstance hazelcastInstance(final Config config) {
    return Hazelcast.getOrCreateHazelcastInstance(config);
  }

Not sure if this helps, but it’s difficult to have an answer to your question as you’re not clear enough about your needs. Have you tried asking on StackOverflow?

Charles  

A very welcome tutorial, thank you. The problem I have is that I cannot get the NoRedirectStrategy to work. My Spring Boot 2.0.2 keeps returning a 302 instead of a 4xx whenever I try to access a protected URL without authentication. Perhaps it is to do with the NoRedirectStrategy being added to the successhandler, and not to a failurehandler? StackOverflow is full of Spring Boot 302 questions but none of these seem to solve the issue. Any pointers greatly appreciated.
Reply

Jerome
In reply to Charles
 

Hi, Make sure you don’t have duplicate Spring Security configuration in your classpath. Check out the sample code i’ve shared on Github, it doesn’t perform any HTTP 302 Moved Temporarily.

Best Regards,

Charles
In reply to Charles
 

Thank you for your response. I finally solved my issue when I realised that I was trying to set up the project using https, not http. I had added a secure channel requirement to the security config. This meant that the server kept redirecting to https, hence the 302. Removing the secure channel in config has resolved the issue for now and I will return to it when I’m ready to move to production. Thanks again for the feedback.

Ferrin Katz  

I was wondering how you would extend this example to include multiple access levels?
Reply

Jerome
In reply to Ferrin Katz
 

Hi Ferrin,

You can very easily manage user access levels by defining multiple roles. Then, you can have fine grain access control in your code by using @PreAuthorize and @PostAuthorize annotations.

Alejandro Bachi  

If you are going to use JWT you don’t need to implement any distributed cache for the user session, just store the user details that you need for authentication and authorization in the token, and keep your application stateless. Here the author kind of miss on the point storing only the username and then getting the user with username from the users service, luckily its and in memory store.. but you wouldnt want that for a production environment. Also storing the username having to iterate over the full collection of users to get the correct user its not very efficient, at least should’ve stored the id to get the user back in around O(1) time from the map instead of O(N) iterating.

This implementation of JWT is also incomplete, you will need to implement some revocation mechanism, like token blacklisting. You will need to google a bit.

Reply

Jerome
In reply to Alejandro Bachi
 

Hi Alejandro,

The code presented here is to get you started. It’s obviously not intended to be used as is in a real application. You’re just pointing flaws made deliberately. The purpose here is not to write an efficient in-memory user store, nore designing a hacker proof JWT token system. The goal is to provide a simple starting point for designing a user login and registration system using Spring Boot and Spring Security.

Regards,

Michal  

Hi Jerome,

Great tutorial, this helped me a lot!

Regards,

Reply
 

Thank you

Your comment has been submitted and will be published once it has been approved.

OK

OOPS!

Your post has failed. Please return to the page and try again. Thank You!

OK