Saturday, April 18, 2020

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.
pom.xml
<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.
PreAuthTokenHeaderFilter.java
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.
AuthTokenSecurityConfig.java
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.
web.xml
<!-- 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.
SpringSecurityInitializer.java
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

API request
HTTP GET http://localhost:8080/SpringRestExample/api/rest/employee-management/employees/
API response
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

API request
HTTP GET http://localhost:8080/SpringRestExample/api/rest/employee-management/employees/
 
AUTH_API_KEY: xyz123
API response
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

API request
HTTP GET http://localhost:8080/SpringRestExample/api/rest/employee-management/employees/
 
AUTH_API_KEY: abcd123456
API response
HTTP Status - 200 OK
 
{
    //response body
}

No comments:

Post a Comment

How to DROP SEQUENCE in Oracle?

  Oracle  DROP SEQUENCE   overview The  DROP SEQUENCE  the statement allows you to remove a sequence from the database. Here is the basic sy...