Spring 在线程或不同请求方面的 SecurityContext 行为是什么?
What is Spring's SecurityContext behavior in terms of threads or different requests?
我正在研究 class 安全性的不同 class 实施。我知道我们将 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);
}
但是,我对这里的 'threads' 是什么意思感到困惑?
- 他们的意思是,与会话无关的单个 HTTP 请求,即对于每个 HTTP 请求,都会有一个新的 ThreadLocal 身份验证对象?
- 或者,它是否特定于 HTTP 会话?即对于用户的会话,将只有一个线程,因此只有一个安全上下文?
以上两点我也有这两个疑惑
- 对于上面的 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);
}
}
}
- 对于上面的 2,如果我在我的安全配置中将 SessionCreationPolicy 设置为无状态 class 那么同样,没有会话,但不同线程上的不同请求。
我在这里对 threads(ThreadLocal SecurityContext) 的解释可能是错误的。
需要帮助。
不知道这个 if 语句在哪里发生,很难评论它是否不必要。如果请求不需要认证,则认证可能为空,但也可能有其他情况。
如果请求确实需要身份验证,那么一旦您的 servlet 被调用,身份验证就不应为空。
线程不绑定到给定的用户会话。使用 Servlet,从线程池中为每个 HTTP 请求分配一个线程。
为每个请求重新建立 SecurityContextHolder
,方法是从会话中提取现有身份验证,或者在您的情况下,从请求数据中提取现有身份验证。
SecurityContextHolder, SecurityContext and Authentication Objects
默认情况下,SecurityContextHolder
使用 ThreadLocal
来存储这些详细信息,这意味着安全上下文始终可用于 同一执行线程中的方法。以这种方式使用 ThreadLocal
是 quite safe if care is taken to clear the thread after the present principal’s request is processed
。当然,Spring 安全会自动为您处理此问题,因此您无需担心。
一些应用程序并不完全适合使用 ThreadLocal
,因为它们使用线程的特定方式。例如,Swing 客户端可能希望 Java 虚拟机中的所有线程使用相同的安全上下文。 SecurityContextHolder
可以在启动时配置策略,以指定您希望如何存储上下文。对于独立应用程序,您可以使用 SecurityContextHolder.MODE_GLOBAL
策略。其他应用程序可能希望安全线程生成的线程也采用相同的安全标识。这是通过使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
实现的。您可以通过两种方式更改默认模式 SecurityContextHolder.MODE_THREADLOCAL
。
第一个是设置一个系统属性,第二个是在SecurityContextHolder
上调用一个静态方法。大多数应用程序不需要更改默认设置,但如果需要,请查看 JavaSecurityContextHolder 文档以了解更多信息。
Storing the SecurityContext between requests
在 Spring 安全性中,在请求之间存储 SecurityContext
的责任落在 SecurityContextPersistenceFilter
身上,它默认将上下文存储为 HttpSession
属性 HTTP 请求。它为每个请求将上下文恢复到 SecurityContextHolder
,并且至关重要的是,当请求完成时 清除 SecurityContextHolder
许多其他类型的应用程序(例如,无状态 RESTful 网络服务)不使用 HTTP 会话,并将在每个请求。但是,将 SecurityContextPersistenceFilter
包含在链中仍然很重要,以确保在每次请求后清除 SecurityContextHolder
。
会话管理
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
将导致 Spring 安全使用 NullSecurityContextRepository
,而不是默认的 HttpSessionSecurityContextRepository
。
这是一个简单的实现,因为它不会将任何内容保存到 HTTP 会话中,并且对于每个请求,创建一个全新的空 SecurityContext,因此没有存储的身份验证等
更新
That means, the below condition is always true if session policy is
stateless. if( SecurityContextHolder.getContext().getAuthentication()
== null )
是的,除非您在条件被调用之前设置了它,否则您将获得 null 身份验证。如果您使用的是 JWT 令牌,您可以验证以下内容并可以设置安全上下文。
@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;
}
我正在研究 class 安全性的不同 class 实施。我知道我们将 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);
}
但是,我对这里的 'threads' 是什么意思感到困惑?
- 他们的意思是,与会话无关的单个 HTTP 请求,即对于每个 HTTP 请求,都会有一个新的 ThreadLocal 身份验证对象?
- 或者,它是否特定于 HTTP 会话?即对于用户的会话,将只有一个线程,因此只有一个安全上下文?
以上两点我也有这两个疑惑
- 对于上面的 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);
}
}
}
- 对于上面的 2,如果我在我的安全配置中将 SessionCreationPolicy 设置为无状态 class 那么同样,没有会话,但不同线程上的不同请求。
我在这里对 threads(ThreadLocal SecurityContext) 的解释可能是错误的。 需要帮助。
不知道这个 if 语句在哪里发生,很难评论它是否不必要。如果请求不需要认证,则认证可能为空,但也可能有其他情况。
如果请求确实需要身份验证,那么一旦您的 servlet 被调用,身份验证就不应为空。
线程不绑定到给定的用户会话。使用 Servlet,从线程池中为每个 HTTP 请求分配一个线程。
为每个请求重新建立
SecurityContextHolder
,方法是从会话中提取现有身份验证,或者在您的情况下,从请求数据中提取现有身份验证。
SecurityContextHolder, SecurityContext and Authentication Objects
默认情况下,SecurityContextHolder
使用 ThreadLocal
来存储这些详细信息,这意味着安全上下文始终可用于 同一执行线程中的方法。以这种方式使用 ThreadLocal
是 quite safe if care is taken to clear the thread after the present principal’s request is processed
。当然,Spring 安全会自动为您处理此问题,因此您无需担心。
一些应用程序并不完全适合使用 ThreadLocal
,因为它们使用线程的特定方式。例如,Swing 客户端可能希望 Java 虚拟机中的所有线程使用相同的安全上下文。 SecurityContextHolder
可以在启动时配置策略,以指定您希望如何存储上下文。对于独立应用程序,您可以使用 SecurityContextHolder.MODE_GLOBAL
策略。其他应用程序可能希望安全线程生成的线程也采用相同的安全标识。这是通过使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
实现的。您可以通过两种方式更改默认模式 SecurityContextHolder.MODE_THREADLOCAL
。
第一个是设置一个系统属性,第二个是在SecurityContextHolder
上调用一个静态方法。大多数应用程序不需要更改默认设置,但如果需要,请查看 JavaSecurityContextHolder 文档以了解更多信息。
Storing the SecurityContext between requests
在 Spring 安全性中,在请求之间存储 SecurityContext
的责任落在 SecurityContextPersistenceFilter
身上,它默认将上下文存储为 HttpSession
属性 HTTP 请求。它为每个请求将上下文恢复到 SecurityContextHolder
,并且至关重要的是,当请求完成时 清除 SecurityContextHolder
许多其他类型的应用程序(例如,无状态 RESTful 网络服务)不使用 HTTP 会话,并将在每个请求。但是,将 SecurityContextPersistenceFilter
包含在链中仍然很重要,以确保在每次请求后清除 SecurityContextHolder
。
会话管理
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
将导致 Spring 安全使用 NullSecurityContextRepository
,而不是默认的 HttpSessionSecurityContextRepository
。
这是一个简单的实现,因为它不会将任何内容保存到 HTTP 会话中,并且对于每个请求,创建一个全新的空 SecurityContext,因此没有存储的身份验证等
更新
That means, the below condition is always true if session policy is stateless. if( SecurityContextHolder.getContext().getAuthentication() == null )
是的,除非您在条件被调用之前设置了它,否则您将获得 null 身份验证。如果您使用的是 JWT 令牌,您可以验证以下内容并可以设置安全上下文。
@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;
}