Ashish
Ashish

Reputation: 1917

What is Spring's SecurityContext behavior in terms of threads or different requests?

I am going through different class implementations of Spring Security. I know that we set the Authentication object into SecurityContext ThreadLocal object as:

UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

upat.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(upat);

So, basically for each thread there is a separate copy of SecurityContext ThreadLocal object which holds the Authentication object for that thread. Fine till here. I have SessionCreationPolicy set to Stateless in my SecurityConfiguration as well. Below is the security configuration:

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");

        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        final CorsConfigurer<HttpSecurity> cors = http.csrf().disable().cors().configurationSource(source);

        final ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry exp =
                cors.and().authorizeRequests();

        exp.antMatchers("/getJWTToken/**").permitAll()
                .antMatchers("/actuator/**").permitAll()
                .antMatchers("/rest/**").authenticated();

        exp.and().exceptionHandling()
                .authenticationEntryPoint(authEntryPoint())
                .and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        ;

        // Add a filter to validate the tokens with every request
        http.addFilterBefore(authRequestFilter(), UsernamePasswordAuthenticationFilter.class);
    }

But, I am confused about what does 'threads' mean here?

  1. Do they mean, individual HTTP requests without having anything to do with session i.e. for each HTTP request there will be a new ThreadLocal Authentication object?
  2. Or, is it specific to an HTTP Session? i.e. for a user's session, there will be only one thread and hence one Security Context?

I have these two doubts as well for both the above points.

  1. For 1 above, if it changes with each request, then why do we need to check for Authentication object in each request's thread as below. I mean, if it is a different thread, there is no need of this. It will definitely be null. (The below if condition exists in the application I am referring to).
if( SecurityContextHolder.getContext().getAuthentication() == null ) {
    if( jwtTokenUtil.validateToken(jwtToken, userObj) )
    {
        if( userObj == null )
        {
            response.setStatus(401);
            return;
        }
        else
        {
            UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken(userObj, null,userObj.getAuthorities());

            upat.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            // After setting the Authentication in the context, we specify
            // that the current user is authenticated. So it passes the
            // Spring Security Configurations successfully.
            SecurityContextHolder.getContext().setAuthentication(upat);
        }
    }
}
  1. For 2 above, if I have SessionCreationPolicy as stateless in my Security Config class then again, there is no session, but different requests on different threads.

I may be wrong in my interpretation of threads(ThreadLocal SecurityContext) here. Need help.

Upvotes: 2

Views: 6225

Answers (2)

Romil Patel
Romil Patel

Reputation: 13727

SecurityContextHolder, SecurityContext and Authentication Objects

By default, the SecurityContextHolder uses a ThreadLocal to store these details, which means that the security context is always available to methods in the same thread of execution. Using a ThreadLocal in this way is quite safe if care is taken to clear the thread after the present principal’s request is processed. Of course, Spring Security takes care of this for you automatically so there is no need to worry about it.

Some applications aren’t entirely suitable for using a ThreadLocal, because of the specific way they work with threads. For example, a Swing client might want all threads in a Java Virtual Machine to use the same security context. SecurityContextHolder can be configured with a strategy on startup to specify how you would like the context to be stored. For a standalone application you would use the SecurityContextHolder.MODE_GLOBAL strategy. Other applications might want to have threads spawned by the secure thread also assume the same security identity. This is achieved by using SecurityContextHolder.MODE_INHERITABLETHREADLOCAL. You can change the mode from the default SecurityContextHolder.MODE_THREADLOCAL in two ways.

The first is to set a system property, the second is to call a static method on SecurityContextHolder. Most applications won’t need to change from the default, but if you do, take a look at the JavaDoc for SecurityContextHolder to learn more.


Storing the SecurityContext between requests

In Spring Security, the responsibility for storing the SecurityContext between requests falls to the SecurityContextPersistenceFilter, which by default stores the context as an HttpSession attribute between HTTP requests. It restores the context to the SecurityContextHolder for each request and, crucially, clears the SecurityContextHolder when the request completes

Many other types of applications (for example, a stateless RESTful web service) do not use HTTP sessions and will re-authenticate on every request. However, it is still important that the SecurityContextPersistenceFilter is included in the chain to make sure that the SecurityContextHolder is cleared after each request.


sessionManagement

.sessionManagement()
     .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

will lead to Spring Security using a NullSecurityContextRepository, instead of the default HttpSessionSecurityContextRepository.

It is a simple implementation, in that it will simply not save anything to the HTTP Session and, for every request, create a completely new and empty SecurityContext, hence with no stored authentication etc.


UPDATE

That means, the below condition is always true if session policy is stateless. if( SecurityContextHolder.getContext().getAuthentication() == null )

Yes, you will get the authentication as null unless you have set it before the condition gets invoked. In case you are using the JWT token, you can verify the same as below and can set the security context.

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
    throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
    String jwt = resolveToken(httpServletRequest);

    if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) {
        Authentication authentication = this.tokenProvider.getAuthentication(jwt);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        ...
    }
    filterChain.doFilter(servletRequest, servletResponse);
}

private String resolveToken(HttpServletRequest request){
    String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
    if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
        return bearerToken.substring(7);
    }
    return null;
}

Upvotes: 8

jzheaux
jzheaux

Reputation: 7762

  1. Without knowing where this if statement is happening, it's hard to comment on whether it's needless. If a request does not require authentication, the authentication may be null, but there may be other cases.

    If a request does require authentication, then once your servlet is invoked, the authentication should not be null.

  2. Threads are not tied to a given user session. With Servlets, a thread is assigned from a thread pool to each HTTP request.

    The SecurityContextHolder is re-established for each request, either by pulling the existing authentication from the session or, in your case, from the request data.

Upvotes: 2

Related Questions