GrantedAuthority
一句话定义:已授予权限,Spring Security 中表示用户拥有的权限或角色的核心接口,以字符串形式承载,角色是其中约定以
ROLE_为前缀的特殊形式。
定义与作用
GrantedAuthority 是 Spring Security 权限体系中最基础的抽象,接口仅定义一个方法:
public interface GrantedAuthority extends Serializable {
String getAuthority(); // 返回权限字符串,如 "ROLE_ADMIN" 或 "SCOPE_read"
}
权限 vs 角色的关系:
| 概念 | 约定前缀 | 示例 | 使用方式 |
|---|---|---|---|
| 角色(Role) | ROLE_ | ROLE_ADMIN、ROLE_USER | hasRole("ADMIN") |
| 权限(Authority) | 无固定前缀 | SCOPE_read、PERM_DELETE、ORDER_CREATE | hasAuthority("SCOPE_read") |
| OAuth2 Scope | SCOPE_ | SCOPE_read、SCOPE_write | hasAuthority("SCOPE_read") |
角色本质上是一种权限,只是 Spring Security 约定用 ROLE_ 前缀标识高层级语义分组。hasRole() 是 hasAuthority() 的快捷方式,会自动补全前缀。
核心原理
ROLE_ 前缀的自动处理机制
// Spring Security 内部源码简化
// ExpressionBasedFilterInvocationSecurityMetadataSource
// 处理 hasRole('ADMIN') 时:
String role = "ADMIN";
String roleWithPrefix = "ROLE_" + role; // "ROLE_ADMIN"
ConfigAttribute config = new SecurityConfig(roleWithPrefix);
// 传递给 RoleVoter 检查
// RoleVoter 的核心检查逻辑
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
for (ConfigAttribute attribute : attributes) {
if (attribute.getAttribute().startsWith("ROLE_")) {
for (GrantedAuthority authority : authentication.getAuthorities()) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return ACCESS_DENIED;
}
UserDetails 中设置权限的两种方式
// 方式一:使用 roles() — 自动添加 ROLE_ 前缀
UserDetails admin = User.builder()
.username("admin")
.password("{noop}admin")
.roles("ADMIN", "USER") // 内部生成 ROLE_ADMIN, ROLE_USER
.build();
// 方式二:使用 authorities() — 原样使用,不添加前缀
UserDetails admin = User.builder()
.username("admin")
.password("{noop}admin")
.authorities("ROLE_ADMIN", "ROLE_USER", "SCOPE_read", "PERM_DELETE")
.build();
⚠️ 关键区别:
roles()和authorities()是互斥的,后调用的会覆盖先调用的。不能同时使用两者。
示例一:ROLE_ 前缀的正确与错误用法
场景说明
配置一个系统,要求区分角色权限和细粒度操作权限。ADMIN 角色有管理权限,USER 角色有读写权限,同时用 SCOPE_read 表示 OAuth2 风格的 scope。
操作前配置(错误示范)
@Configuration
@EnableWebSecurity
public class WrongAuthorityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("{noop}admin")
.authorities("ADMIN") // ❌ 错误:想用 hasRole("ADMIN") 但没带前缀
.and()
.withUser("user")
.password("{noop}user")
.roles("USER") // ✅ 正确:生成 ROLE_USER
.and()
.withUser("api")
.password("{noop}api")
.authorities("SCOPE_read", "SCOPE_write"); // ✅ 权限方式
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.antMatchers("/admin/**").hasRole("ADMIN") // 检查 ROLE_ADMIN
.antMatchers("/user/**").hasRole("USER") // 检查 ROLE_USER
.antMatchers("/api/**").hasAuthority("SCOPE_read") // 检查 SCOPE_read
.anyRequest().authenticated()
)
.formLogin(withDefaults());
}
}
结果分析(错误)
admin用户访问/admin/dashboard:hasRole("ADMIN")转换为ROLE_ADMINRoleVoter检查admin用户的权限列表:["ADMIN"]- 没有
ROLE_ADMIN→ACCESS_DENIED→ 403 Forbidden
操作后配置(修正)
@Configuration
@EnableWebSecurity
public class CorrectAuthorityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("{noop}admin")
.roles("ADMIN") // ✅ 正确:生成 ROLE_ADMIN
.and()
.withUser("user")
.password("{noop}user")
.roles("USER")
.and()
.withUser("api")
.password("{noop}api")
.authorities("SCOPE_read", "SCOPE_write");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.GET, "/api/**").hasAuthority("SCOPE_read")
.antMatchers(HttpMethod.POST, "/api/**").hasAuthority("SCOPE_write")
.anyRequest().authenticated()
)
.formLogin(withDefaults());
}
}
结果分析(正确)
| 用户 | 权限列表 | 访问 /admin/dashboard | 访问 /api/data |
|---|---|---|---|
| admin | [ROLE_ADMIN] | ✅ hasRole("ADMIN") 匹配 | ✅ ADMIN 默认无 SCOPE_read,但 hasAnyRole 不包含此路径... 实际 403 |
| user | [ROLE_USER] | ❌ 403 | ❌ 无 SCOPE_read |
| api | [SCOPE_read, SCOPE_write] | ❌ 403 | ✅ hasAuthority("SCOPE_read") 匹配 |
示例二:自定义 GrantedAuthority 实现数据范围权限
场景说明
除了角色,还需要控制用户能访问哪些部门的数据。自定义 GrantedAuthority 携带额外元数据。
操作前配置
public class DataScopeGrantedAuthority implements GrantedAuthority {
private final String authority; // 权限标识
private final String deptCode; // 部门编码(数据范围)
private final int dataLevel; // 数据级别:1=全部 2=本部门 3=仅本人
public DataScopeGrantedAuthority(String authority, String deptCode, int dataLevel) {
this.authority = authority;
this.deptCode = deptCode;
this.dataLevel = dataLevel;
}
@Override
public String getAuthority() {
return authority;
}
public String getDeptCode() { return deptCode; }
public int getDataLevel() { return dataLevel; }
}
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) {
// 从数据库加载用户及其部门信息
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
// 添加带数据范围的权限
authorities.add(new DataScopeGrantedAuthority(
"DATA_DEPT", "D1001", 2 // 本部门数据权限
));
return new org.springframework.security.core.userdetails.User(
username, "{noop}password", authorities
);
}
}
@Service
public class ReportService {
@Autowired
private SecurityContextService securityContextService;
@PreAuthorize("hasRole('USER')")
public List<Report> getReports() {
// 从 SecurityContext 获取 DataScopeGrantedAuthority
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
DataScopeGrantedAuthority dataScope = auth.getAuthorities().stream()
.filter(a -> a instanceof DataScopeGrantedAuthority)
.map(a -> (DataScopeGrantedAuthority) a)
.findFirst()
.orElseThrow();
// 根据数据级别过滤查询
if (dataScope.getDataLevel() == 2) {
return reportRepository.findByDeptCode(dataScope.getDeptCode());
}
return reportRepository.findAll();
}
}
结果分析
普通
USER登录后调用getReports():- 方法级别
@PreAuthorize("hasRole('USER')")通过 - 获取
DataScopeGrantedAuthority中dataLevel=2, deptCode=D1001 - 查询条件
WHERE dept_code = 'D1001'→ 只返回本部门报表
- 方法级别
ADMIN用户(dataLevel=1)调用getReports():- 查询条件无限制 → 返回全部报表
易错场景:roles() 和 authorities() 混用导致权限丢失
场景:开发者同时调用 roles() 和 authorities() 期望用户同时拥有角色和权限。
// 错误示范
UserDetails user = User.builder()
.username("admin")
.password("{noop}admin")
.roles("ADMIN") // 设置 ROLE_ADMIN
.authorities("SCOPE_read", "SCOPE_write") // ❌ 覆盖了 roles() 的设置!
.build();
问题:User.UserBuilder 内部,roles() 和 authorities() 都操作同一个 authorities 集合。后调用的会覆盖前者。
源码分析:
// UserBuilder 源码简化
public UserBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
return authorities(authorities); // 直接调用 authorities(),覆盖原有
}
public UserBuilder authorities(String... authorities) {
// 同样覆盖
this.authorities = new ArrayList<>();
for (String authority : authorities) {
this.authorities.add(new SimpleGrantedAuthority(authority));
}
return this;
}
正确做法:
// 方法一:全部用 authorities(),显式添加 ROLE_ 前缀
UserDetails user = User.builder()
.username("admin")
.password("{noop}admin")
.authorities("ROLE_ADMIN", "SCOPE_read", "SCOPE_write")
.build();
// 方法二:分开构建后合并
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
authorities.add(new SimpleGrantedAuthority("SCOPE_read"));
authorities.add(new SimpleGrantedAuthority("SCOPE_write"));
UserDetails user = User.builder()
.username("admin")
.password("{noop}admin")
.authorities(authorities) // 一次性传入完整列表
.build();
面试考点:
问:
User.builder().roles("ADMIN").authorities("SCOPE_read").build()后用户有哪些权限? 答:用户只有SCOPE_read一个权限,ROLE_ADMIN被覆盖了。roles()和authorities()在UserBuilder内部都操作同一个authorities集合,后调用者覆盖前者。如果需要同时设置角色和自定义权限,应统一使用authorities()并显式写出ROLE_ADMIN。
权限字符串速查表
| 前缀 | 含义 | 典型来源 | 检查方式 |
|---|---|---|---|
ROLE_ | 角色 | UserDetailsService / 数据库 | hasRole() / hasAnyRole() |
SCOPE_ | OAuth2 Scope | JWT / OAuth2 Token | hasAuthority() |
PERM_ | 自定义操作权限 | 数据库权限表 | hasAuthority() |
IS_AUTHENTICATED_ | 认证状态 | Spring Security 内置 | isAuthenticated() / isFullyAuthenticated() |