✅ 核心原理:@PreAuthorize
通过 SecurityContext
获取当前用户信息
@PreAuthorize
本身只是一个"声明式权限注解",它并不直接知道用户是谁、有什么角色。它的能力来自于 Spring Security 在运行时提供的上下文信息(Security Context)。
这个上下文里保存了当前登录用户的详细信息,包括:
- 用户名
- 密码(通常不暴露)
- 权限(Granted Authorities) ← 这就是角色的来源
- 是否已认证
🔁 完整流程解析
1️⃣ 用户发起请求(如:curl -u admin:123456 ...
)
HTTP Basic 认证将用户名密码放在请求头中:
Authorization: Basic YWRtaW46MTIzNDU2
Spring Security 拦截该请求,提取用户名 admin
和密码 123456
。
2️⃣ Spring Security 调用 UserDetailsService.loadUserByUsername()
这是最关键的一环!
你定义了:
java
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) {
// 从数据库或缓存中查用户
// 构建 UserDetails 对象,包含用户名、密码、权限(角色)
return new User(username, password, authorities);
}
}
其中 authorities
是一个 Collection<? extends GrantedAuthority>
,比如:
java
List<GrantedAuthority> authorities =
Collections.singletonList(new SimpleGrantedAuthority("ROLE_ADMIN"));
👉 重点来了 :
这里的 "ROLE_ADMIN"
就是角色信息,它被封装进了 UserDetails
,最终放入 SecurityContext
。
3️⃣ 认证成功后,用户信息存入 SecurityContext
Spring Security 会创建一个 Authentication
对象,包含:
principal
: 用户详情(就是你返回的UserDetails
实例)credentials
: 密码(认证后通常设为 null)authorities
: 权限列表authenticated
: true
然后把这个 Authentication
放入 SecurityContext
:
java
SecurityContextHolder.getContext().setAuthentication(authentication);
从此,整个应用都可以通过 SecurityContextHolder
获取当前用户信息。
4️⃣ 当请求到达 @PreAuthorize
方法时,Spring AOP 拦截并执行权限检查
例如:
java
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String deleteUser() { ... }
Spring 会:
- 从
SecurityContext
中取出Authentication
- 获取
Authentication.getAuthorities()
- 检查这些权限中是否包含
ROLE_ADMIN
java
// 伪代码
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
boolean hasRole = authorities.stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
如果 hasRole == true
,放行;否则抛出 AccessDeniedException
。
🧠 hasRole()
和 hasAuthority()
的区别
方法 | 说明 |
---|---|
hasRole('ADMIN') |
等价于 hasAuthority('ROLE_ADMIN') ,自动加 ROLE_ 前缀 |
hasAuthority('ROLE_ADMIN') |
精确匹配权限字符串 |
所以你在 GrantedAuthority
中存的是 "ROLE_ADMIN"
,就应该用 hasRole('ADMIN')
或 hasAuthority('ROLE_ADMIN')
。
📌 总结:@PreAuthorize
是如何知道角色的?
步骤 | 说明 |
---|---|
1️⃣ | 用户登录,Spring Security 提取用户名密码 |
2️⃣ | 调用你实现的 CustomUserDetailsService.loadUserByUsername() |
3️⃣ | 你从数据库/缓存中查出用户,并构造 UserDetails ,其中包含 GrantedAuthority (如 ROLE_ADMIN ) |
4️⃣ | 认证成功,Authentication 对象存入 SecurityContext |
5️⃣ | 请求到达 @PreAuthorize 方法,Spring 从 SecurityContext 取出 Authentication |
6️⃣ | 检查 authorities 列表是否包含所需角色 |
✅ | 决定是否放行 |
💡 举个例子:
java
private UserDetails buildUserDetails(String username, String password, String role) {
Collection<? extends GrantedAuthority> authorities =
Collections.singletonList(new SimpleGrantedAuthority(role)); // 👈 role = "ROLE_ADMIN"
return new org.springframework.security.core.userdetails.User(username, password, authorities);
}
这里你把数据库查到的 role
字段(值为 "ROLE_ADMIN"
)封装成了一个 GrantedAuthority
,它就会被 Spring Security 识别为用户的角色。
🛠️ 验证:你可以在代码中打印当前用户信息
java
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/debug")
public String debug() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println("当前用户: " + auth.getName());
System.out.println("用户权限: " + auth.getAuthorities());
System.out.println("是否认证: " + auth.isAuthenticated());
return "Check console";
}
输出:
当前用户: admin
用户权限: [ROLE_ADMIN]
是否认证: true
✅ 结论 :
@PreAuthorize
能知道用户角色,是因为你通过 UserDetailsService
把角色信息加载进了 UserDetails
,而 Spring Security 把它放进了全局上下文 SecurityContext
,@PreAuthorize
只是读取了这个上下文而已。
如果你不把角色放进 authorities
,@PreAuthorize
就"不知道"这个角色。