Search code examples
spring-bootspring-securityspring-data-jpaspring-cache

Spring security getAuthentication return null?


I'm a newbie. My spring boot version is 2.7.0

This is my security configurattion

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final CustomAuthorizationFilter customAuthorizationFilter;

    /**
     * Filter beans are registered with the servlet container automatically.
     * CustomAuthorizationFilter with @Component annotation.
     * CustomAuthorizationFilter is added to servlet filter chain and security filter chain.
     * Hence, it is executed twice (it is also registered twice).
     */
    @Bean
    public FilterRegistrationBean registration(CustomAuthorizationFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean(filter);
        registration.setEnabled(false);
        return registration;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors();
        http.csrf().disable(); //because don't use form login
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);

        http.authorizeRequests().antMatchers(
                API_V1_AUTH_ROOT_URL + "/**",
                API_V1_COMMON_ROOT_URL + "/**").permitAll();
        http.authorizeRequests().antMatchers(
                API_V1_USER_ROOT_URL + "/**",
                API_V1_COURSE_ROOT_URL + "/**").authenticated();
        http.authorizeRequests().antMatchers("/", "/index", "/css/*", "/js/*").permitAll();

        http.addFilterBefore(customAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

}

This is CustomAuthorizationFilter

@Slf4j
@RequiredArgsConstructor
@Component
public class CustomAuthorizationFilter extends OncePerRequestFilter {
    private final JWTProvider jwtProvider;
    private final UserService userService;

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException
    {
        String uri = request.getServletPath();
        AntPathMatcher pathMatcher = new AntPathMatcher();

        boolean matchIgnoreUrl = EXCLUDE_URL_PATTERNS.stream()
                .anyMatch(p -> pathMatcher.match(p, uri));

        log.info("[{}] matchIgnoreUrl is {}", uri, matchIgnoreUrl);
        if (matchIgnoreUrl) return true;
        else {
            boolean matchedAuthenticateUrl = AUTHENTICATE_URL_PATTERNS.stream()
                    .anyMatch(p -> pathMatcher.match(p, uri));

            log.info("[{}] MatchedAuthenticateUrl is {}", uri, matchedAuthenticateUrl);
            //Ignore query database if request is a missing url page
            return !matchedAuthenticateUrl;
        }
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Principal principal = request.getUserPrincipal();
        String uri = request.getRequestURI();
        log.info("[{}] Match in Should Filter", uri);
        String authorizationHeader = request.getHeader(AUTHORIZATION);

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            String token = authorizationHeader.substring("Bearer ".length());

            // return null
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            
            String username = jwtProvider.getUsernameFromToken(token);

            //Query to database every request come
            UserDetails userDetails = userService.loadUserByUsername(username);
            
            //Set authentication to Security Context
            SecurityUtils.authenticateUser(request, userDetails);

        }

        filterChain.doFilter(request, response);
    }
}

The idea is: Authentication object used in controllers.

  1. The first request is sent to the server, the server queries the database to get the user's information and sets the authentication object to the security context.
  2. Next request if same user (username-base), i want the server to not need to query the database. It will use Authentication object from first request.

The problem

  1. In the second request, Authentication object is null, why ???
  2. How can i implement my idea ???
  3. Is there another way to validate a token as valid (eg. username decoded from token can be inactivate) that optimizes the number of database queries per request ??? (is the caching ???)
  4. Is it that every time the request completes, the security context will be cleaned ???

Solution

    • 4. Yes, SecurityContextHolder in ServletWeb is default using TheadLocal. So it'll be clear when the thread (your request) ends.
    • 1. 4 is the reason
    • 2. You should use session/cookie instead of JWT. Normally, when using JWT, we expect a STATELESS service.
    • 3. To validate the token, using your signing key is enough, don't need to call DB, just need to keep your key secret. Something like:
    import io.jsonwebtoken.Jws;
    
    Jwts.parserBuilder()
        .setSigningKey(yourKey)
        .build()
        .parseClaimsJws(yourToken);
    

    Your service now is secured based on the characteristic of JWT token.