Spring Boot: Authentication with custom HTTP header

For the last few months we’ve been working on a Spring Boot project and one of the more challenging aspects has been wrangling Spring’s security component. For the project, we were looking to authenticate users using a custom HTTP header that contained a token generated from a third party service. There doesn’t seem to be a whole lot of concrete examples on how to set something like this up so here’s some notes from the trenches. Note: I’m still new to Spring so if any of this is inaccurate, let me know in the comments.

Concretely, what we’re looking to do is authenticate a user by passing a value in an X-Authorization HTTP header. So for example using cURL or jQuery:

:~$ curl -H "X-Authorization: $some_secret_token" http://localhost/user
$.ajax({
url: 'http://localhost/user',
headers: { 'X-Authorization': '$some_secret_token' }
});
view raw gistfile1.txt hosted with ❤ by GitHub

In addition to insuring that the token is valid, we also want to setup Spring Security so that we can access the user’s details using “SecurityContextHolder.getContext().getAuthentication()”. So how do you do this? Turns out, you need a couple of classes to make this work:

  • An Authentication Token: You need a class that extends AbstractAuthenticationToken so that you can let Spring know about your authenticated user. The UsernamePasswordAuthenticationToken class is a pretty good starting point.
  • The Filter: You’ll need to create a filter to inspect requests that you want authenticated, grab the X-Authentication filter, confirm that it’s a valid token, and set the corresponding Authentication. Since we only want this to run once per request you can extend the OncePerRequestFilter class to set this up. You can see an example class below:
    import java.io.IOException;
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.web.filter.OncePerRequestFilter;
    public class DemoAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
    HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {
    String xAuth = request.getHeader("X-Authorization");
    // validate the value in xAuth
    if(isValid(xAuth) == false){
    throw new SecurityException();
    }
    // The token is 'valid' so magically get a user id from it
    Long id = getUserIdFromToken(xAuth);
    // Create our Authentication and let Spring know about it
    Authentication auth = new DemoAuthenticationToken(id);
    SecurityContextHolder.getContext().setAuthentication(auth);
    filterChain.doFilter(request, response);
    }
    }
  • An Authentication Provider: The final piece is a class that extends AuthenticationProvider which handles retrieving a JPA entity from the database. By implementing an AuthenticationProvider instead of doing the database lookup in the filter, you can keep your filter framework agnostic by not having to autowire in a JPA repository. My implementation looks similar to:
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.AuthenticationProvider;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.stereotype.Component;
    import com.pearson.reader.error.UnknownUserException;
    import com.pearson.reader.models.User;
    import com.pearson.reader.repositories.UserRepository;
    @Component
    public class DemoAuthenticationProvider implements AuthenticationProvider {
    // This would be a JPA repository to snag your user entities
    private final UserRepository userRepository;
    @Autowired
    public DemoAuthenticationProvider(UserRepository userRepository) {
    this.userRepository = userRepository;
    }
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    DemoAuthenticationToken demoAuthentication = (DemoAuthenticationToken) authentication;
    User user = userRepository.find(demoAuthentication.getId());
    if(user == null){
    throw new UnknownUserException("Could not find user with ID: " + demoAuthentication.getId());
    }
    return user;
    }
    @Override
    public boolean supports(Class<?> authentication) {
    return DemoAuthenticationToken.class.isAssignableFrom(authentication);
    }
    }

And finally, the last step is to wire this all up. You’ll need a class that extends WebSecurityConfigurerAdapter with two ovveridden configure methods to configure the filter and the authentication provider. For example, the following works at a bare minimum:

import org.springframework.beans.factory.annotation.Autowired;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfigDemo extends WebSecurityConfigurerAdapter {
@Autowired
private DemoAuthenticationProvider demoAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatcher("/user")
.addFilterBefore(new DemoAuthenticationFilter(), BasicAuthenticationFilter.class)
;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(demoAuthenticationProvider);
}
}

And then finally to access the authenticated user from a controller you’d do:

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = (User) auth.getPrincipal();
view raw getuser.java hosted with ❤ by GitHub

Anyway, hope this helps and as mentioned above if there’s anything inaccurate feel free to post in the comments.