I'm new to spring, so just looking for a general direction / advice.
I'm building a small microservice. The service auth is already setup to validate a JWT that is passed to it (issued by a different microservice).
The JWT contains a sessionid claim. I want to be able to get that claim, and validate that the session is still active.
For now that will be by querying the database directly, although in future when we have a dedicated session-service, I will change it to call that service instead.
I can obviously get the claim in each controller that should validate the session (basically all of them), but I am looking for a more global option, like a request interceptor (that only triggers AFTER the JWT is validated, and has access to the validated JWT).
Is there a recommended way to do this kind of thing in spring?
CodePudding user response:
You have several options to do that: using a ControllerAdvice
, perhaps AOP, but probably the way to go will be using a custom security filter.
The idea is exemplified in this article and this related SO question.
First, create a filter for processing the appropriate claim. For example:
public class SessionValidationFilter extends GenericFilterBean {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String AUTHORIZATION_BEARER = "Bearer";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// We will provide our own validation logic from scratch
// If you are using Spring OAuth or something similar
// you can instead use the already authenticated token, something like:
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// if (authentication != null && authentication.getPrincipal() instanceof Jwt) {
// Jwt jwt = (Jwt) authentication.getPrincipal();
// String sessionId = jwt.getClaimAsString("sessionid");
// ...
// Resolve token from request
String jwt = getTokenFromRequest(httpServletRequest);
if (jwt == null) {
// your choice... mine
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// If the token is not valid, raise error
if (!this.validateToken(jwt)) {
throw new BadCredentialsException("Session expired");
}
// Continue filter chain
filterChain.doFilter(servletRequest, servletResponse);
}
// Resolve token from Authorization header
private String getTokenFromRequest(HttpServletRequest request){
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(bearerToken) && bearerToken.startsWith(AUTHORIZATION_BEARER)) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
// Validate the JWT token
// We can use the jjwt library, for instance, to process the JWT token claims
private boolean validateToken(String token) {
try {
Claims claims = Jwts.parser()
// .setSigningKey(...)
.parseClaimsJws(token)
.getBody()
;
String sessionId = (String)claims.get("sessionid");
// Process as appropriate to determine whether the session is valid or not
//...
return true;
} catch (Exception e) {
// consider logging the error. Handle as appropriate
}
return false;
}
}
Now, assuming for instance that you are using Java configuration, add the filter to the Spring Security filter chain after the one that actually is performing the authentication:
@Configuration
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
// the rest of your code
@Override
protected void configure(HttpSecurity http) throws Exception {
// the rest of your configuration
// Add the custom filter
// see https://docs.spring.io/spring-security/site/docs/5.2.1.RELEASE/reference/htmlsingle/#filter-stack
// you can name every provided filter or any specified that is included in the filter chain
http.addFilterAfter(new SessionValidationFilter(), BasicAuthenticationFilter.class);
}
}