Spring Security 中获取用户信息的原理与实践
在 Web 应用中,Spring Security 能够在每次请求时识别用户身份并验证权限,核心在于 SecurityContextPersistenceFilter 对 SecurityContext 的管理。本文结合源码解析这一机制的实现原理,并介绍在实际开发中获取用户信息的常用方式。
SecurityContext 的生命周期管理
SecurityContext 是存储用户认证信息的容器,其生命周期由 SecurityContextPersistenceFilter 全程管理,确保每个请求都能获取到当前用户的身份信息。
1. 核心流程:请求到来时加载上下文
当一个请求进入应用时,SecurityContextPersistenceFilter 会执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) { chain.doFilter(request, response); return; } request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
HttpRequest HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try { SecurityContextHolder.setContext(contextBeforeChainExecution); chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); SecurityContextHolder.clearContext(); repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); } }
|
关键作用:
- 通过
ThreadLocal 让当前请求的所有组件(过滤器、控制器等)都能便捷访问用户信息;
- 请求结束后清除
ThreadLocal 内容,避免线程复用导致的信息泄露。
2. 从 Session 加载 SecurityContext
loadContext 方法由 HttpSessionSecurityContextRepository 实现,负责从 Session 中读取或创建 SecurityContext:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { HttpServletRequest request = requestResponseHolder.getRequest(); HttpServletResponse response = requestResponseHolder.getResponse(); HttpSession httpSession = request.getSession(false);
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) { context = generateNewContext(); }
SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper( response, request, httpSession != null, context); requestResponseHolder.setResponse(wrappedResponse);
return context; }
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) { if (httpSession == null) { return null; } Object contextFromSession = httpSession.getAttribute(springSecurityContextKey); return (contextFromSession instanceof SecurityContext) ? (SecurityContext) contextFromSession : null; }
|
3. 将 SecurityContext 保存回 Session
请求处理完成后,saveContext 方法将更新后的 SecurityContext 存储到 Session 中,确保用户身份信息在会话期间保持一致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils .getNativeResponse(response, SaveContextOnUpdateOrErrorResponseWrapper.class); if (!responseWrapper.isContextSaved()) { responseWrapper.saveContext(context); } }
protected void saveContext(SecurityContext context) { final Authentication authentication = context.getAuthentication(); HttpSession httpSession = request.getSession(false);
if (authentication == null || trustResolver.isAnonymous(authentication)) { if (httpSession != null) { httpSession.removeAttribute(springSecurityContextKey); } return; }
if (httpSession == null) { httpSession = createNewSessionIfAllowed(context); }
if (httpSession != null) { if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) { httpSession.setAttribute(springSecurityContextKey, context); } } }
|
在 Web 应用中获取用户信息的常用方式
基于上述机制,我们可以在控制器、服务层等任意组件中获取当前登录用户的信息,核心是通过 SecurityContextHolder 访问 SecurityContext。
1. 直接通过 SecurityContextHolder 获取
这是最通用的方式,适用于任何组件(控制器、服务、工具类等):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails;
public class UserInfoUtil {
public static String getCurrentUsername() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { return null; } Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails) { return ((UserDetails) principal).getUsername(); } return principal.toString(); }
public static Collection<? extends GrantedAuthority> getCurrentAuthorities() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return authentication != null ? authentication.getAuthorities() : Collections.emptyList(); } }
|
2. 在控制器中通过 @AuthenticationPrincipal 注解获取
Spring Security 提供了 @AuthenticationPrincipal 注解,可直接在控制器方法参数中注入当前用户的 UserDetails 或自定义用户对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class UserController { @GetMapping("/user/info") public String getUserInfo(@AuthenticationPrincipal UserDetails userDetails) { return "当前登录用户:" + userDetails.getUsername() + ",权限:" + userDetails.getAuthorities(); } @GetMapping("/user/custom") public String getCustomUser(@AuthenticationPrincipal CustomUser customUser) { return "用户ID:" + customUser.getId() + ",用户名:" + customUser.getUsername(); } }
public class CustomUser implements UserDetails { private Long id; private String username; private String password; private Collection<? extends GrantedAuthority> authorities; }
|
3. 通过 HttpServletRequest 获取
在 Web 环境中,用户信息也会被存入 HttpServletRequest 的属性中,可通过以下方式获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import javax.servlet.http.HttpServletRequest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class RequestController { @GetMapping("/user/from-request") public String getUserFromRequest(HttpServletRequest request) { UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) request.getUserPrincipal(); if (auth != null) { return "从请求中获取的用户:" + auth.getName(); } return "未登录"; } }
|
关键对象解析
- SecurityContextHolder:
- 静态工具类,通过
ThreadLocal 存储 SecurityContext;
- 提供
getContext() 和 setContext() 方法操作上下文。
- SecurityContext:
- 存储用户认证信息的容器,核心方法是
getAuthentication();
- 本质是一个接口,默认实现为
SecurityContextImpl。
- Authentication:
- 存储用户身份信息(用户名、密码、权限等);
- 认证成功后包含
UserDetails 类型的主体(principal)。
- UserDetails:
- 封装用户核心信息(用户名、密码、权限、账号状态等);
- 通常需要自定义实现以扩展用户属性(如用户 ID、昵称等)。
特殊场景处理
匿名用户:
异步线程中获取用户信息:
总结
Spring Security 通过 SecurityContextPersistenceFilter 实现了 SecurityContext 与 Session 的绑定,结合 ThreadLocal 确保每个请求都能便捷访问用户信息:
- 请求到来时,从 Session 加载
SecurityContext 并存入 SecurityContextHolder;
- 请求处理过程中,通过
SecurityContextHolder 或注解获取用户信息;
- 请求结束后,将
SecurityContext 保存回 Session 并清除 ThreadLocal
v1.3.10