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 } |
No comments:
Post a Comment