I'm trying to setup Spring Boot 3 to use both authentication using JWT and HTTP sessions with X-Auth tokens. The goal is to use X-Auth tokens as primary authentication method, but users might authenticate using an external provider which grants a JWT access token.
I've successfully managed to create two different authorization endpoints, one at /auth
using form based login and returns an X-Auth token, and one at /authJwt
. JWT authorization is only enabled at /authJwt
and all other endpoints are protected using X-Auth tokens.
Is it possible to enable generation of X-Auth tokens by authentication using a JWT? I've configured HTTP sessions to always be created, and a call to /authJwt
returns an X-Auth token in the HTTP header. But the X-Auth token is not valid when trying to authenticate.
This is the security configuration which I'm using (I've removed some irrelevant parts):
@Configuration
@EnableWebSecurity()
public class WebSecurityConfiguration {
// Endpoints which will be public and not require authentication
private static final String[] AUTH_WHITELIST = {
"/auth"
};
/**
* Filter chain for authenticating using JWT tokens
*/
@Bean
@Order(1)
public SecurityFilterChain oAuth2ResourceFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.securityMatcher("/authJwt")
.cors().and().csrf().disable()
.requestCache().disable().exceptionHandling().and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and()
.authorizeHttpRequests().anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return httpSecurity.build();
}
/**
* Filter chain for enabling authentication.
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().and().csrf().disable()
.requestCache().disable().exceptionHandling().and()
.formLogin().loginPage("/auth").usernameParameter("loginName").passwordParameter("loginPassword")
.successHandler((request, response, authentication) -> response.setStatus(HttpServletResponse.SC_OK))
.and()
.authorizeHttpRequests(requests -> requests
.requestMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated()
)
// Return 401 on no session
.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.logout();
return httpSecurity.build();
}
}
This is the configuration for the sessions:
@Configuration()
@EnableSpringHttpSession
public class SpringHttpSessionConfig {
@Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}
@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
}
Can anyone point in the correct direction of exchanging JWT tokens for X-Auth tokens?
I realized I had assumed I needed to use Spring Session, but it was easier to solve it without sessions. Instead I added a custom token store and token filters which manages authorization. The token store:
@Component
public class TokenStore {
private final ConcurrentHashMap<String, Authentication> authenticationCache
= new ConcurrentHashMap<>();
/**
* Generates and registers authentication token.
*
* @param authentication The authentication used.
* @return Generated authentication token.
*/
public String generateToken(Authentication authentication) {
String token = UUID.randomUUID().toString();
authenticationCache.put(token, authentication);
return token;
}
/**
* Returns authentication from authentication token.
*
* @param token The authentication token to check.
* @return Authentication if token exists, otherwise null.
*/
public Authentication getAuth(String token) {
return authenticationCache.getOrDefault(token, null);
}
/**
* Removes authentication token.
*
* @param token Authentication token.
*/
public void removeAuth(String token) {
authenticationCache.remove(token);
}
}
Token filter which is added to the SecurityFilterChain
to validate access tokens:
@Component
public class TokenFilter extends OncePerRequestFilter {
private final TokenStore tokenStore;
public TokenFilter(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
/**
* Checks if an authentication token is valid and sets authentication.
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authToken = request.getHeader(AuthConstants.AUTH_TOKEN_NAME);
if (authToken != null) {
Authentication authentication = tokenStore.getAuth(authToken);
if (authentication != null) {
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
SecurityContextHolder.setContext(securityContext);
}
}
filterChain.doFilter(request, response);
}
}
The tokens are genereated either on the success handler of the form login:
private void successHandler(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
String token = tokenStore.generateToken(authentication);
response.addHeader(AuthConstants.AUTH_TOKEN_NAME, token);
}
Or in the endpoint for the /authJwt
endpoint:
/**
* Exchanges a valid JWT to an access token.
*
* This endpoint is protected using the OAuth2 filter chain.
*/
@PostMapping(value = "/authJwt")
public void verifyClient(HttpServletResponse response, Authentication authentication) {
String token = tokenStore.generateToken(authentication);
response.addHeader(AuthConstants.AUTH_TOKEN_NAME, token);
}