java - 就线程或不同请求而言,Spring 的 SecurityContext 行为是什么?

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

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

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

因此,基本上每个线程都有一个 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);
    }

但是,我对这里的“线程”是什么意思感到困惑?

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

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

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

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

最佳答案

SecurityContextHolder, SecurityContext and Authentication Objects

默认情况下,SecurityContextHolder 使用 ThreadLocal 来存储这些详细信息,这意味着安全上下文始终可用于 同一执行线程中的方法。以这种方式使用 ThreadLocal如果在处理当前委托(delegate)人的请求后注意清除线程 是非常安全的。当然,Spring Security 会自动为您处理此事,因此您无需担心。

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

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


Storing the SecurityContext between requests

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

许多其他类型的应用程序(例如,无状态 RESTful 网络服务)不使用 HTTP session ,并且会在每次请求时重新验证。但是,将 SecurityContextPersistenceFilter 包含在链中以确保在每次请求后清除 SecurityContextHolder 仍然很重要。


session 管理

.sessionManagement()
     .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

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

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


更新

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

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

@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;
}

https://stackoverflow.com/questions/67424274/

相关文章:

r - 将列中以冒号分隔的字符串拆分为 R 中的不同列

fortran - Fortran 中的嵌套名单

mongodb - 使用 GridFS 在 MongoDB 中存储图像是否有效?

javascript - 使用 ts-node 的 Typescript 声明合并无法按预期工作

regex - 如何将 RegEx token 传递给 RegEx 替换中的 PowerShell

javascript - 获取具有类的下一个元素(不是子元素或兄弟元素)

reactjs - 创建一个可以通过函数调用显示的 React 组件(如 react-toastif

python - 有没有办法缩短多个 if 语句?

python - 如何处理来自 binance websocket 的多流数据?

r - 使用数值条件对一系列列进行编码