乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

GrantedAuthority

一句话定义:已授予权限,Spring Security 中表示用户拥有的权限或角色的核心接口,以字符串形式承载,角色是其中约定以 ROLE_ 为前缀的特殊形式。


定义与作用

GrantedAuthority 是 Spring Security 权限体系中最基础的抽象,接口仅定义一个方法:

public interface GrantedAuthority extends Serializable {
    String getAuthority();  // 返回权限字符串,如 "ROLE_ADMIN" 或 "SCOPE_read"
}

权限 vs 角色的关系:

概念约定前缀示例使用方式
角色(Role)ROLE_ROLE_ADMIN、ROLE_USERhasRole("ADMIN")
权限(Authority)无固定前缀SCOPE_read、PERM_DELETE、ORDER_CREATEhasAuthority("SCOPE_read")
OAuth2 ScopeSCOPE_SCOPE_read、SCOPE_writehasAuthority("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_ADMIN
    • RoleVoter 检查 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 ScopeJWT / OAuth2 TokenhasAuthority()
PERM_自定义操作权限数据库权限表hasAuthority()
IS_AUTHENTICATED_认证状态Spring Security 内置isAuthenticated() / isFullyAuthenticated()
上一页
授权模型
下一页
AccessDecisionManager