一、会话(Session)的基本概念
会话是服务器与客户端之间持续交互的机制。在 Web 应用中,每当用户通过浏览器访问应用时,服务器通常会为其创建一个唯一的会话标识(Session ID),并通过 Cookie(通常是 JSESSIONID
)将该标识返回给客户端。客户端在后续的每次请求中都会携带这个 Session ID,从而使服务器能够识别出该请求属于哪个用户的会话。
二、Spring Security 如何使用 Session 维持认证状态(有状态会话)
在默认配置下,Spring Security 利用 HTTP Session 来存储用户的认证信息。这意味着一旦用户通过认证(例如通过表单登录),其认证状态会被保存到 Session 中,从而在后续的请求中无需重新认证。
(1)认证成功后的认证信息存储
当用户成功通过认证(例如通过表单登录或其他认证方式),Spring Security 会执行以下步骤:
-
创建
Authentication
对象: 认证成功后,Spring Security 会生成一个已认证的Authentication
对象,包含用户的详细信息和权限。 -
存储到
SecurityContextHolder
:SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authResult); // authResult 是已认证的 Authentication 对象 SecurityContextHolder.setContext(context);
-
存储到
HTTP Session
: Spring Security 的SecurityContextPersistenceFilter
会将SecurityContextHolder
中的SecurityContext
保存到HTTP Session
中。这样,用户的认证状态在会话期间保持有效。// SecurityContextPersistenceFilter 内部逻辑示例 HttpSession session = request.getSession(false); if (session != null) {session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); }
(2)每次请求时加载认证信息
当用户在同一会话中发起新的请求时,Spring Security 会执行以下步骤:
-
从
HTTP Session
中加载SecurityContext
:SecurityContextPersistenceFilter
会在请求开始时,从HTTP Session
中提取存储的SecurityContext
对象,并将其设置到SecurityContextHolder
中。// SecurityContextPersistenceFilter 内部逻辑示例 HttpSession session = request.getSession(false); if (session != null) {SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);if (context != null) {SecurityContextHolder.setContext(context);} }
-
访问
SecurityContextHolder
: 任何在处理请求的代码(如控制器、服务层、授权注解等)都可以通过SecurityContextHolder.getContext().getAuthentication()
来获取当前用户的认证信息,无需重新认证。
(3)请求结束后的认证信息保存
请求处理完成后,SecurityContextPersistenceFilter
会将当前线程的 SecurityContext
再次保存回 HTTP Session
,以确保认证信息在会话中持续有效。
// SecurityContextPersistenceFilter 内部逻辑示例
HttpSession session = request.getSession(false);
if (session != null) {session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
}
三、相关组件详解
(1)SecurityContextHolder
-
作用:是 Spring Security 用来存储
SecurityContext
的核心类。SecurityContext
中包含了当前用户的Authentication
对象。 -
存取方式:通过静态方法
SecurityContextHolder.getContext()
和SecurityContextHolder.setContext()
来获取和设置SecurityContext
。 -
存储策略:默认使用
ThreadLocal
存储,确保每个线程(即每个请求)都有自己的安全上下文(SecurityContext)
。
(2)SecurityContextPersistenceFilter
-
作用:负责在每次请求开始时从 HTTP Session 中加载
SecurityContext
,并在请求结束时将SecurityContext
保存回 Session。 -
执行顺序:位于过滤器链的较前位置,确保在认证和授权之前加载认证信息。
-
配置:默认情况下,Spring Security 自动注册此过滤器。通常不需要手动配置,除非有特殊需求。
(3)HttpSessionSecurityContextRepository
-
作用:
SecurityContextPersistenceFilter
使用的存储机制,负责将SecurityContext
存储到 HTTP Session 中。 -
配置:默认使用
HttpSessionSecurityContextRepository
,但也可以自定义实现,比如无状态认证(详见后文)。
四、无状态认证与 Session 管理
尽管默认情况下 Spring Security 使用 Session 来维持认证状态,但在某些场景下(如 RESTful API 或微服务架构),可能更倾向于无状态认证方式(例如基于 JWT)。在这种情况下,可以配置 Spring Security 使其不依赖于 HTTP Session,而是通过每个请求携带的 Token 来进行认证。
配置无状态认证的做法
-
禁用 Session 管理:
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 其他配置
SessionCreationPolicy.STATELESS
表示 Spring Security 不会创建或使用 HTTP Session。 -
移除
SecurityContextPersistenceFilter
:在无状态模式下,
SecurityContextPersistenceFilter
不再需要,因为不使用 Session 来存储SecurityContext
。但通常通过设置SessionCreationPolicy.STATELESS
,框架会自动调整过滤器链。 -
使用 Token 过滤器:
自定义一个过滤器(如
JwtAuthenticationFilter
)来从每个请求中提取 Token(如 JWT),验证其有效性,并将认证信息设置到SecurityContextHolder
中。public class JwtAuthenticationFilter extends OncePerRequestFilter {private final AuthenticationManager authenticationManager;private final JwtService jwtService;public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtService jwtService) {this.authenticationManager = authenticationManager;this.jwtService = jwtService;}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {String authHeader = request.getHeader("Authorization");if (authHeader != null && authHeader.startsWith("Bearer ")) {String token = authHeader.substring(7);// 验证 Token 并构建 Authentication 对象Authentication auth = jwtService.parseToken(token);if (auth != null) {SecurityContextHolder.getContext().setAuthentication(auth);}}filterChain.doFilter(request, response);} }
五、会话保持的优缺点
优点:
- 简化认证流程:无需在每次请求中重复发送凭证,服务器通过 Session 维护用户状态。
- 自动管理:Spring Security 自动处理 Session 中的认证信息加载与存储。
缺点:
- 扩展性限制:在分布式系统中,管理和同步 Session 可能变得复杂,需要额外的配置(如分布式 Session 存储)。
- 资源消耗:每个活跃用户都占用服务器的 Session 资源,可能影响可扩展性。
- 不适用于无状态服务:对于需要完全无状态的 RESTful API,更推荐使用基于 Token 的认证方式。