Session Management and Access Token Validation on every request in Spring Security + Spring Boot

107 Views Asked by At

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:

  1. Is it good to store the access_token details inside the session and use the token information from the session if access_token expires?
  2. 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>'

0

There are 0 best solutions below