Spring的SecurityContext在线程或不同请求方面的行为是什么?

Kno*_*ads 2 java spring web-applications spring-security spring-boot

我正在研究 Spring Security 的不同类实现。我知道我们将Authentication对象设置为SecurityContext ThreadLocal 对象,如下所示:

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

upat.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(upat);
Run Code Online (Sandbox Code Playgroud)

因此,基本上每个线程都有一个单独的 SecurityContext ThreadLocal 对象副本,它保存该线程的 Authentication 对象。到这里就好了。我也在我的 SecurityConfiguration 中将 SessionCreationPolicy 设置为 Stateless。以下是安全配置:

    @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);
    }
Run Code Online (Sandbox Code Playgroud)

但是,我对“线程”在这里的含义感到困惑?

  1. 他们的意思是,单独的 HTTP 请求与会话没有任何关系,即对于每个 HTTP 请求,都会有一个新的 ThreadLocal 身份验证对象?
  2. 或者,它特定于 HTTP 会话吗?即对于用户会话,只有一个线程,因此只有一个安全上下文?

对于以上两点,我也有这两点疑问。

  1. 对于上面的 1,如果它随着每个请求而变化,那么为什么我们需要在每个请求的线程中检查 Authentication 对象,如下所示。我的意思是,如果是不同的线程,则不需要这个。肯定会为空。(如果我所指的应用程序中存在以下条件)。
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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 对于上面的 2,如果我在安全配置类中将 SessionCreationPolicy 设置为无状态,那么同样没有会话,但不同线程上有不同的请求。

我在这里对线程(ThreadLocal SecurityContext)的解释可能是错误的。需要帮忙。

Pat*_*mil 7

SecurityContextHolder、SecurityContext 和身份验证对象

\n

默认情况下,SecurityContextHolder使用 aThreadLocal来存储这些详细信息,这意味着安全上下文始终可供同一执行线程中的方法使用。ThreadLocal以这种方式使用 a是quite safe if care is taken to clear the thread after the present principal\xe2\x80\x99s request is processed. 当然,Spring Security 会自动为您处理这个问题,因此无需担心。

\n

某些应用程序\xe2\x80\x99t 完全适合使用ThreadLocal,因为它们使用线程的特定方式。例如,Swing 客户端可能希望 Java 虚拟机中的所有线程使用相同的安全上下文。SecurityContextHolder可以在启动时配置策略来指定您希望如何存储上下文。对于独立应用程序,您将使用该SecurityContextHolder.MODE_GLOBAL策略。其他应用程序可能希望安全线程生成的线程也采用相同的安全身份。这是通过使用 来实现的SecurityContextHolder.MODE_INHERITABLETHREADLOCAL。您可以通过两种方式更改默认模式SecurityContextHolder.MODE_THREADLOCAL

\n

第一个是设置系统属性,第二个是调用 上的静态方法SecurityContextHolder。大多数应用程序不需要更改默认值,但如果需要,请查看 SecurityContextHolder 的 JavaDoc 以了解更多信息。

\n
\n

在请求之间存储 SecurityContext

\n

在 Spring Security 中,存储SecurityContext请求之间的信息的责任落在了SecurityContextPersistenceFilter,它默认将上下文存储为HTTP 请求HttpSession之间的属性。它将上下文恢复到每个请求,并且最重要的是,在请求完成时清除 SecurityContextHolderSecurityContextHolder

\n

许多其他类型的应用程序(例如,无状态 RESTful Web 服务)不使用HTTP 会话,并将对每个请求重新进行身份验证SecurityContextPersistenceFilter然而,将 包含在链中以确保SecurityContextHolder在每次请求后清除仍然很重要。

\n
\n

会话管理

\n
.sessionManagement()\n     .sessionCreationPolicy(SessionCreationPolicy.STATELESS)\n
Run Code Online (Sandbox Code Playgroud)\n

将导致 Spring Security 使用NullSecurityContextRepository, 而不是默认的HttpSessionSecurityContextRepository.

\n

这是一个简单的实现,因为它不会将任何内容保存到 HTTP Session,并且对于每个请求,创建一个全新的空 SecurityContext,因此没有存储的身份验证等。

\n
\n

更新

\n
\n

这意味着,如果会话策略是无状态的,则以下条件始终为真。if( SecurityContextHolder.getContext().getAuthentication()\n== null )

\n
\n

是的,除非您在调用条件之前设置了身份验证,否则您将获得空身份验证。如果您使用 JWT 令牌,您可以验证如下内容并可以设置安全上下文。

\n
@Override\npublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)\n    throws IOException, ServletException {\n    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;\n    HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;\n    String jwt = resolveToken(httpServletRequest);\n\n    if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) {\n        Authentication authentication = this.tokenProvider.getAuthentication(jwt);\n        SecurityContextHolder.getContext().setAuthentication(authentication);\n        ...\n    }\n    filterChain.doFilter(servletRequest, servletResponse);\n}\n\nprivate String resolveToken(HttpServletRequest request){\n    String bearerToken = request.getHeader(AUTHORIZATION_HEADER);\n    if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {\n        return bearerToken.substring(7);\n    }\n    return null;\n}\n
Run Code Online (Sandbox Code Playgroud)\n