Spring REST Custom Token Authentication Example

Learn to add custom token based authentication to REST APIs using created with Spring REST and Spring security 5. In given example, a request with header name “AUTH_API_KEY” with a predefined value will pass through. All other requests will return HTTP 403 response.

1. Spring security dependencies

Include following dependencies to work with spring security classes and interfaces.

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-core</artifactId>
	<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
	<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
	<version>5.1.5.RELEASE</version>
</dependency>

2. Extend AbstractPreAuthenticatedProcessingFilter

Create a class and extend AbstractPreAuthenticatedProcessingFilter. It is a base class for processing filters that handle pre-authenticated authentication requests, where it is assumed that the principal has already been authenticated by an external system.

By default, the filter chain will proceed when an authentication attempt fails in order to allow other authentication mechanisms to process the request. It helps in passing the request to other security filters (e.g. form login) if token is found invalid.

It’s getPreAuthenticatedPrincipal() method helps to read the auth header value from the current request.

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.web.authentication
			.preauth.AbstractPreAuthenticatedProcessingFilter;

public class PreAuthTokenHeaderFilter 
		extends AbstractPreAuthenticatedProcessingFilter {

	private String authHeaderName;

	public PreAuthTokenHeaderFilter(String authHeaderName) {
		this.authHeaderName = authHeaderName;
	}

	@Override
	protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
		return request.getHeader(authHeaderName);
	}

	@Override
	protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
		return "N/A";
	}
}

It is optional approach. An application may decide to simply return auth failed error immediately as well.

3. Configure AuthenticationManager and add to HttpSecurity

We need to set the authentication manager which will handle the auth process and decide how to process the success and failure scenarios.

After adding authentication manager, we can add PreAuthTokenHeaderFilter to HttpSecurity.

If any auth error is raised, it will be handled by default ExceptionTranslationFilter which forward it to default auth error page in spring. If you want to show the auth error response differently, you need to create custom ExceptionTranslationFilter class.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
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.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;

@Configuration
@EnableWebSecurity
@PropertySource("classpath:application.properties")
@Order(1)
public class AuthTokenSecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${howtodoinjava.http.auth.tokenName}")
    private String authHeaderName;

    //TODO: retrieve this token value from data source
    @Value("${howtodoinjava.http.auth.tokenValue}")
    private String authHeaderValue;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception 
    {
    	PreAuthTokenHeaderFilter filter = new PreAuthTokenHeaderFilter(authHeaderName);
        
        filter.setAuthenticationManager(new AuthenticationManager() 
        {
            @Override
            public Authentication authenticate(Authentication authentication) 
            									throws AuthenticationException 
            {
                String principal = (String) authentication.getPrincipal();
                
                if (!authHeaderValue.equals(principal))
                {
                    throw new BadCredentialsException("The API key was not found "
                    							+ "or not the expected value.");
                }
                authentication.setAuthenticated(true);
                return authentication;
            }
        });
        
        httpSecurity.
            antMatcher("/api/**")
            .csrf()
            	.disable()
            .sessionManagement()
            	.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            	.addFilter(filter)
            	.addFilterBefore(new ExceptionTranslationFilter(
                    new Http403ForbiddenEntryPoint()), 
            			filter.getClass()
                )
            	.authorizeRequests()
            		.anyRequest()
            		.authenticated();
    }

}

4. Register security filter

Traditionally, spring security had starting point in web.xml file in XML based configuration with DelegatingFilterProxy.

<!-- Spring Security -->
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy
	</filter-class>
</filter>

<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

In Java config, we can achieve the same effect by exteding the class AbstractSecurityWebApplicationInitializer.

import org.springframework.security.web.context
			.AbstractSecurityWebApplicationInitializer;
public class SpringSecurityInitializer 
			extends AbstractSecurityWebApplicationInitializer {
    //no code needed
}

4. Spring REST Custom Token Authentication Demo

4.1. Without auth token in header

HTTP GET http://localhost:8080/SpringRestExample/api/rest/employee-management/employees/
HTTP Status - 403 – Forbidden
Type Status - Report
Message Access - Denied
Description - The server understood the request but refuses to authorize it.

4.2. Incorrect auth token in header

HTTP GET http://localhost:8080/SpringRestExample/api/rest/employee-management/employees/

AUTH_API_KEY: xyz123
HTTP Status - 403 – Forbidden
Type Status - Report
Message Access - Denied
Description - The server understood the request but refuses to authorize it.

4.2. Valid auth token in header

HTTP GET http://localhost:8080/SpringRestExample/api/rest/employee-management/employees/

AUTH_API_KEY: abcd123456
HTTP Status - 200 OK

{
	//response body
}

Happy Learning !!

Was this post helpful?

Join 7000+ Fellow Programmers

Subscribe to get new post notifications, industry updates, best practices, and much more. Directly into your inbox, for free.

4 thoughts on “Spring REST Custom Token Authentication Example”

  1. I want to add multiple ant matchers because I need to have some end points not secured. httpSecurity builder will let me add multiple antmatchers, but only recognizes the last one added. How can I say end points not secured ?

    Reply
  2. String principal = (String) authentication.getPrincipal();
    

    I get an exception from that statement from whatever embedded Spring Boot server I use.

    "message": "io.undertow.servlet.util.IteratorEnumeration cannot be cast to java.lang.String",
        "trace": "java.lang.ClassCastException: io.undertow.servlet.util.IteratorEnumeration cannot be cast to java.lang.String\n\tat uk.co.nesistec.contractpicturechallenge.config.APISecurityConfig.lambda$authFilter$0(APISecurityConfig.java:49)
    
    Reply

Leave a Comment

HowToDoInJava

A blog about Java and its related technologies, the best practices, algorithms, interview questions, scripting languages, and Python.