- 一、 AuthenticationManager 接口
- 二、Authentication接口
- 三、SecurityContextHolder
- Spring Security 中的
SecurityContextHolder
详解 - 1.SecurityContextHolder 作用
- 2. SecurityContextHolder 的结构
- 3. SecurityContext 与 Authentication
- 4. SecurityContextHolder 认证流程
- 5. SecurityContextHolder 的存储策略
- 6. SecurityContextHolder 的常见使用场景
- 7. 线程安全问题
- 8. 总结
在Spring Security中认证(Authentication)和授权(Authorization)是独立的,是分开的。一切授权操作都是在认证成功后的基础之上进行的,如果身份不合法,那也就没有授权的必要了。
一、 AuthenticationManager 接口
1. AuthenticationManager是什么?
AuthenticationManager
是一个接口,它定义了一个核心方法:
Authentication authenticate(Authentication authentication) throws AuthenticationException;
- 参数:
Authentication
对象,包含用户提供的凭证信息,例如用户名和密码。 - 返回值:如果认证成功,返回一个经过认证的
Authentication
对象,表示用户身份已验证;如果失败,则抛出AuthenticationException
异常。
简单来说,AuthenticationManager
的职责是对用户提交的凭证进行验证,确保用户身份合法。
2. AuthenticationManager的主要实现:ProviderManager
在Spring Security中,AuthenticationManager
最常见的实现类是 ProviderManager
。ProviderManager
通过协调一组 AuthenticationProvider
来完成认证工作。
AuthenticationProvider
接口AuthenticationProvider
是另一个关键接口,定义了两个主要方法:Authentication authenticate(Authentication authentication)
:执行具体的认证逻辑。boolean supports(Class<?> authentication)
:判断该 provider 是否支持当前传入的Authentication
类型。
ProviderManager
的工作机制ProviderManager
内部维护了一个AuthenticationProvider
列表。当它的authenticate
方法被调用时:- 遍历
AuthenticationProvider
列表。 - 找到第一个支持当前
Authentication
类型的AuthenticationProvider
(通过supports
方法判断)。 - 调用该
AuthenticationProvider
的authenticate
方法进行认证。 - 如果认证成功,返回一个已认证的
Authentication
对象;如果失败,抛出异常。
- 遍历
这种设计使得 AuthenticationManager
具有很高的灵活性,可以支持多种认证方式(例如用户名/密码认证、OAuth2、LDAP 等),只需配置不同的 AuthenticationProvider
即可。
3. 常用的AuthenticationProvider:DaoAuthenticationProvider
在实际应用中,一个常用的 AuthenticationProvider
实现是 DaoAuthenticationProvider
。它主要用于基于数据库的用户名/密码认证,工作流程如下:
- 加载用户信息:通过
UserDetailsService
从数据库中获取用户信息(UserDetails
对象)。 - 密码验证:将用户提交的密码与数据库中的密码进行比对(通常结合密码加密器,如
BCryptPasswordEncoder
)。 - 返回结果:如果验证通过,返回一个认证成功的
Authentication
对象;否则抛出异常。
开发者可以通过自定义 UserDetailsService
来指定如何从数据源加载用户信息,从而实现个性化的认证逻辑。
4. 其他AuthenticationManager实现
除了 ProviderManager
,Spring Security 还提供了一些其他的 AuthenticationManager
实现,例如:
JaasAuthenticationManager
:基于 Java Authentication and Authorization Service (JAAS) 进行认证,适用于需要集成 JAAS 的场景。- 自定义实现:开发者可以实现
AuthenticationManager
接口,创建完全定制化的认证逻辑。
5. AuthenticationManager在认证流程中的角色
AuthenticationManager
是 Spring Security 认证流程的核心协调者。它的典型使用场景如下:
- 用户提交登录请求(例如用户名和密码)。
- Spring Security 创建一个未认证的
Authentication
对象(通常是UsernamePasswordAuthenticationToken
)。 AuthenticationManager
的authenticate
方法被调用。ProviderManager
委托合适的AuthenticationProvider
处理认证。- 认证结果被返回并存储到安全上下文中(
SecurityContext
),供后续授权使用。
6. 总结
AuthenticationManager
是 Spring Security 中负责用户认证的核心接口。ProviderManager
是其主要实现,通过管理多个AuthenticationProvider
来支持不同的认证方式。DaoAuthenticationProvider
是一个常用的 provider,适合基于数据库的用户名/密码认证。- 通过灵活的设计,
AuthenticationManager
能够轻松扩展,支持各种认证场景。
理解 AuthenticationManager
的工作原理,有助于开发者更好地配置和定制 Spring Security 的认证流程,从而满足不同的安全需求。
二、Authentication接口
在 Spring Security 中,Authentication
是一个核心接口,用于表示用户的认证信息。它不仅包含用户提交的凭证(例如用户名和密码),还包含认证成功后的用户权限等信息。下面我们将详细讲解 Authentication
接口及其在 Spring Security 认证过程中的作用。
1. Authentication 接口简介
Authentication
接口定义了用户认证信息的主要内容,通过以下方法提供访问:
getAuthorities()
:返回用户拥有的权限集合(例如角色或权限列表),类型为Collection<? extends GrantedAuthority>
。getCredentials()
:返回用户的凭证,通常是密码、证书等。getDetails()
:返回用户的额外信息,例如 IP 地址或会话 ID。getPrincipal()
:返回用户的主体信息,通常是用户名或UserDetails
对象。isAuthenticated()
:返回一个布尔值,表示用户是否已通过认证。setAuthenticated(boolean)
:设置认证状态,通常由 Spring Security 框架内部调用。
这些方法使得 Authentication
成为一个灵活的载体,能够同时表示认证请求和认证结果。
2. Authentication 的两种状态
Authentication
对象在认证过程中有两种主要状态:
未认证状态:
- 当用户提交凭证(例如用户名和密码)时,Spring Security 会创建一个未认证的
Authentication
对象。 - 此时,
isAuthenticated()
返回false
,表示凭证尚未被验证。 - 例如,一个
UsernamePasswordAuthenticationToken
对象会被初始化为包含用户名和密码,但认证状态为未完成。
- 当用户提交凭证(例如用户名和密码)时,Spring Security 会创建一个未认证的
已认证状态:
- 认证成功后,Spring Security 会生成一个新的
Authentication
对象。 - 这个对象包含用户的权限(
authorities
)、主体(principal
)等信息,且isAuthenticated()
返回true
。 - 例如,认证后的
UsernamePasswordAuthenticationToken
会包含UserDetails
对象和权限列表。
- 认证成功后,Spring Security 会生成一个新的
3. Authentication 的常见实现类
Spring Security 提供了多种 Authentication
接口的实现类,以支持不同的认证场景:
UsernamePasswordAuthenticationToken
:- 最常用的实现,适用于基于用户名和密码的认证。
- 未认证时包含用户名和密码,认证成功后包含
UserDetails
和权限。
AnonymousAuthenticationToken
:- 表示匿名用户,通常用于未登录用户的访问。
RememberMeAuthenticationToken
:- 用于“记住我”功能,支持用户在关闭浏览器后仍保持登录状态。
JwtAuthenticationToken
:- 用于基于 JWT(JSON Web Token)的认证,常见于 OAuth2 场景。
4. Authentication 在认证流程中的作用
Authentication
在 Spring Security 的认证流程中起着关键作用,以下是其工作流程:
用户提交凭证:
- 用户通过登录表单输入用户名和密码。
- Spring Security 创建一个未认证的
UsernamePasswordAuthenticationToken
对象,封装用户名和密码。
认证请求:
AuthenticationManager
的authenticate
方法接收这个未认证的Authentication
对象。- 具体的
AuthenticationProvider
(例如DaoAuthenticationProvider
)负责验证凭证,比如通过数据库检查用户名和密码是否匹配。
认证成功:
- 如果凭证有效,
AuthenticationProvider
返回一个已认证的Authentication
对象。 - 这个对象包含用户的权限、主体等信息,且认证状态为
true
。
- 如果凭证有效,
存储到安全上下文:
- 认证成功后,
Authentication
对象被存储到SecurityContextHolder
中,供后续的授权和访问控制使用。
- 认证成功后,
5. Authentication 使用示例
在大多数情况下,开发者无需直接操作 Authentication
对象,因为 Spring Security 的过滤器和配置会自动完成认证流程。但在某些自定义场景中,可能需要手动创建或访问 Authentication
对象。
示例 1:手动创建未认证的 Authentication 对象
Authentication authentication = new UsernamePasswordAuthenticationToken("username", "password");
这个对象可以传递给 AuthenticationManager
进行认证。
示例 2:获取当前用户的认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
String username = authentication.getName(); // 获取用户名
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); // 获取权限
// 处理用户信息
}
6. 总结
Authentication
是 Spring Security 中表示用户认证信息的核心接口,包含凭证、权限、主体等内容。- 它在认证前后有未认证和已认证两种状态,分别对应认证请求和认证结果。
- Spring Security 提供了多种实现类(如
UsernamePasswordAuthenticationToken
),支持不同的认证方式。 - 在认证流程中,
Authentication
对象从用户提交凭证开始,经过验证后存储到安全上下文,用于后续的权限控制。
理解 Authentication
的作用和工作机制,可以帮助开发者更好地掌握 Spring Security 的认证流程,从而实现安全、高效的用户认证功能。
三、SecurityContextHolder
Spring Security 中的 SecurityContextHolder
详解
SecurityContextHolder
是 Spring Security 中最核心的类之一,它用于存储和获取当前线程的安全上下文(SecurityContext
),其中包含了当前用户的 身份认证信息(Authentication
)。
1.SecurityContextHolder 作用
- 存储认证信息:
SecurityContextHolder
持有SecurityContext
,SecurityContext
内部存储着Authentication
(当前用户身份)。 - 全局访问认证信息:无论在 Controller、Service 还是其他组件,都可以通过
SecurityContextHolder.getContext()
访问当前认证信息。 - 支持不同的存储策略:默认使用
ThreadLocal
,即 线程级别存储,保证当前请求线程中的身份信息不会被其他线程访问。
2. SecurityContextHolder 的结构
核心组成
public class SecurityContextHolder {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
public static SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
public static void setContext(SecurityContext context) {
contextHolder.set(context);
}
public static void clearContext() {
contextHolder.remove();
}
}
getContext()
:获取当前线程的安全上下文,默认使用ThreadLocal
进行存储。setContext(SecurityContext context)
:手动设置安全上下文。clearContext()
:清除当前线程的安全上下文,避免线程复用时信息泄漏。
3. SecurityContext 与 Authentication
SecurityContext作用
SecurityContext
是 SecurityContextHolder
内部存储的对象,用于持有 Authentication
信息:
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
getAuthentication()
:获取当前认证的Authentication
。setAuthentication(Authentication authentication)
:手动设置认证信息。
示例:获取当前用户身份
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
4. SecurityContextHolder 认证流程
默认认证流程
- 用户提交 用户名+密码 进行登录。
AuthenticationManager.authenticate()
认证用户。- 认证成功后,Spring Security 将
Authentication
存入SecurityContextHolder
:SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
- 后续请求时,可以通过
SecurityContextHolder
获取当前用户信息。
5. SecurityContextHolder 的存储策略
Spring Security 默认使用 ThreadLocal
存储 SecurityContext
,但也支持其他存储策略:
策略 | 描述 |
---|---|
MODE_THREADLOCAL (默认) |
使用 ThreadLocal ,每个线程独立存储安全上下文 |
MODE_INHERITABLETHREADLOCAL |
允许子线程继承 SecurityContext |
MODE_GLOBAL |
适用于无状态应用,所有线程共享同一个 SecurityContext |
更改存储策略
在 Spring Security 启动前 设置:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
6. SecurityContextHolder 的常见使用场景
6.1 在 Controller 获取当前用户
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/me")
public ResponseEntity<String> getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return ResponseEntity.ok("Current User: " + authentication.getName());
}
}
6.2 在 Service 层获取当前用户
@Service
public class UserService {
public String getCurrentUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
return authentication.getName();
}
return "Anonymous";
}
}
6.3 在 Spring Security 过滤器中手动设置 SecurityContext
如果使用 JWT 认证,我们通常需要手动设置 SecurityContextHolder
:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtService jwtService, UserDetailsService userDetailsService) {
this.jwtService = jwtService;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = extractToken(request);
if (token != null && jwtService.validateToken(token)) {
String username = jwtService.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
- 从 HTTP 头解析 JWT 令牌。
- 验证令牌 并解析用户信息。
- 构造
Authentication
并存入SecurityContextHolder
。
7. 线程安全问题
7.1 ThreadLocal
问题
SecurityContextHolder
默认使用 ThreadLocal
,但在多线程环境(如异步任务)中,认证信息可能无法正确传递:
@Async
public void someAsyncTask() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println(authentication.getName()); // 可能为 null
}
解决方案
使用 MODE_INHERITABLETHREADLOCAL
:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
或者手动传递 SecurityContext
:
@Async
public void someAsyncTask(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println(authentication.getName());
}
8. 总结
SecurityContextHolder
是 Spring Security 认证信息的全局存储容器,默认使用ThreadLocal
进行存储。SecurityContext
内部存储Authentication
,用于保存当前用户身份信息。- 可以在
Controller
、Service
或Filter
中访问当前用户:SecurityContextHolder.getContext().getAuthentication();
- 在 JWT 或 SSO 认证场景,需要手动设置
SecurityContextHolder
。 - 多线程环境下需小心
ThreadLocal
,可以使用MODE_INHERITABLETHREADLOCAL
或手动传递SecurityContext
。
SecurityContextHolder
在整个 Spring Security 体系中扮演着 身份认证信息存储 的关键角色,确保应用能够安全、高效地进行身份认证管理。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 需要 ADMIN 角色
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // 需要 USER 或 ADMIN 角色
.anyRequest().authenticated(); // 其他请求需要认证
}
}
在上述配置中,hasRole(“ADMIN”) 和 hasAnyRole(“USER”, “ADMIN”) 会被转换为 ConfigAttribute 对象。