I tried the following thing to achieve my application use case.
Usecase:
- It is ok for Access Token to expire immediately after the identity has been asserted – the user continues to access the resource based on the component’s session lifecycle configuration.
- when the new request comes with the access_token, spring security doing token validation(As Default).
- after the validated token, user details & other information are stored inside a tomcat in-memory session.
- when any request comes next time with the JSESSIONID & access_token spring security will check if a valid Session exists in memory, if yes do not use the access_token for this request, else go for the access_token validation.
Why I am doing this?
- I don't want to validate the token on every request(for optimization), because the same user has validated at the first, and auth details are stored inside the session.
My Question Is:
- Is it good to store the access_token details inside the session and use the token information from the session if access_token expires?
- what does spring security suggest for this use case or any good way to do it?
Resouce Server Implementation Details:
- Spring Security Adapter:
package org.cfx.resouce.server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoders;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class ResouceServerAdapter {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(new CookieAuthenticationFilter(), BearerTokenAuthenticationFilter.class);
http.addFilterAfter(new SecurityContextHolderSetterAfterAccessTokenValidation(), BearerTokenAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and().csrf().disable();
http.authorizeRequests().antMatchers("/msg").authenticated()
.antMatchers("/test").permitAll().anyRequest().denyAll().and().oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return JwtDecoders.fromIssuerLocation("http://host:port/openam/oauth2/Test");
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthoritiesClaimName("groups");
converter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtAuthenticationConverter;
}
}
- Custom filter implementation:
package org.cfx.resouce.server;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
public class CookieAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ImmutablePair<String, String> resolvedHeaders = Utils.resolveHeaders(request);
//String accessToken = resolvedHeaders.left;
String cookieValue = resolvedHeaders.right; //BFAA1E95A63DA333CDBFACE6786FCC26
// if cookie present then need to check inside session
if (cookieValue != null) {
System.out.println("Cookie Value Found Inside Header........");
HttpSession session = request.getSession(false);
String sessionId = session != null ? session.getId() : null;
System.out.println("------Session Found with ID: --------: " + sessionId);
System.out.println("Session :: "
+ (session != null ? session.getAttribute("SPRING_SECURITY_CONTEXT") : null));
if (session != null && session.getAttribute("SPRING_SECURITY_CONTEXT") != null) {
System.out.println("--------------- Session ID Found ------------ ");
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication((Authentication) session.getAttribute("SPRING_SECURITY_CONTEXT"));
SecurityContextHolder.setContext(context);
System.out.println("Removing the Authorization Header......");
// Remove the header for Bearer Token Validation to skip BearerTokenAuthenticationFilter.
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request) {
@Override
public String getHeader(String name) {
if (name.equalsIgnoreCase(HttpHeaders.AUTHORIZATION)) {
// Remove the header
return null;
}
return super.getHeader(name);
}
};
filterChain.doFilter(requestWrapper, response);
} else {
System.out.println("Go for Bearer token validation because session not found.....");
filterChain.doFilter(request, response);
}
} else {
System.out.println("Go for Bearer token validation.....");
filterChain.doFilter(request, response);
}
}
}
package org.cfx.resouce.server;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
public class SecurityContextHolderSetterAfterAccessTokenValidation extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ImmutablePair<String, String> resolvedHeaders = Utils.resolveHeaders(request);
String accessToken = resolvedHeaders.left;
// if cookie present then need to check inside session
if (accessToken != null) {
// Session doesn't exist, fall back to token-based authentication
Authentication context = SecurityContextHolder.getContext().getAuthentication();
System.out.println("Context :: " + context);
HttpSession newSession = request.getSession(true);
newSession.setAttribute("SPRING_SECURITY_CONTEXT", context); // SPRING_SECURITY_CONTEXT
System.out.println(
"Session ID for Newly Created Session::<<<<<< " + newSession.getId() + " >>>>>>>>>>>>>>>>>>> ");
filterChain.doFilter(request, response);
} else {
System.out.println("Auth Done By Cookie Value...");
filterChain.doFilter(request, response);
}
}
}
- Application Main Class:
package org.cfx.resouce.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class ResouceServerMainApp extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ResouceServerMainApp.class, args);
}
}
application.properties file:
server.port=8080
server.servlet.context-path=/server
server.shutdown=graceful
#logging.level.org.quartz=DEBUG
#logging.level.org.apache.tomcat.util=DEBUG
#logging.level.org.springframework=DEBUG
logging.level.org.springframework.security=TRACE
#logging.level.com.zaxxer.hikari=DEBUG
#server.tomcat.accesslog.enabled=true
#logging.level.org.apache.tomcat=DEBUG
#logging.level.org.apache.catalina=DEBUG
#org.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true
Curl Command:
curl --location 'http://localhost:8080/server/msg' --header 'Cookie: JSESSIONID=<JSESSION ID GOES HERE>' --header 'Authorization: Bearer <ACCESS_TOKEN GOES HERE>'