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

    • 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
    • 最佳实践

RoleHierarchy

一句话定义:角色层级继承机制,定义角色之间的上下级关系,使高层级角色自动拥有低层级角色的所有权限,避免重复配置和权限膨胀。


定义与作用

在实际系统中,角色通常存在层级关系。例如 ADMIN 自然应该拥有 USER 的所有权限,而 SUPER_ADMIN 又应拥有 ADMIN 的所有权限。如果没有层级继承,必须为每个用户在数据库中存储所有权限,导致数据冗余和维护困难。

RoleHierarchy 接口让 Spring Security 在权限检查时自动展开角色继承链,将高层级角色隐式转换为低层级角色参与投票。

public interface RoleHierarchy {
    // 传入用户实际拥有的权限,返回包含继承后所有权限的集合
    Collection<? extends GrantedAuthority> getReachableGrantedAuthorities(
        Collection<? extends GrantedAuthority> authorities);
}

典型层级关系:

SUPER_ADMIN > ADMIN > STAFF > USER > GUEST

拥有 SUPER_ADMIN 的用户自动拥有 ADMIN、STAFF、USER、GUEST 的所有权限。


核心原理

RoleHierarchyImpl 的工作机制

public class RoleHierarchyImpl implements RoleHierarchy {
    private String hierarchy;  // 层级字符串配置

    @Override
    public Collection<? extends GrantedAuthority> getReachableGrantedAuthorities(
        Collection<? extends GrantedAuthority> authorities) {
        // 遍历传入的权限,递归查找所有可达(继承)的权限
        // 如传入 ROLE_ADMIN,返回 [ROLE_ADMIN, ROLE_USER, ROLE_GUEST]
    }
}

配置解析示例:

@Bean
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy(
        "ROLE_ADMIN > ROLE_STAFF\n" +
        "ROLE_STAFF > ROLE_USER\n" +
        "ROLE_USER > ROLE_GUEST"
    );
    return hierarchy;
}

Spring Security 内部会将该字符串解析为有向图,然后通过可达性算法计算每个角色的继承权限。

与 RoleVoter 的集成


示例一:基础角色层级配置

场景说明

企业系统有四级角色:超级管理员、管理员、普通员工、访客。上级自动拥有下级所有权限。

操作前配置(无角色层级,权限冗余)

@Configuration
@EnableWebSecurity
public class NoHierarchyConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("super")
                .password("{noop}super")
                .authorities("ROLE_SUPER_ADMIN", "ROLE_ADMIN", "ROLE_STAFF", "ROLE_USER")
            .and()
            .withUser("admin")
                .password("{noop}admin")
                .authorities("ROLE_ADMIN", "ROLE_STAFF", "ROLE_USER")
            .and()
            .withUser("staff")
                .password("{noop}staff")
                .authorities("ROLE_STAFF", "ROLE_USER")
            .and()
            .withUser("user")
                .password("{noop}user")
                .authorities("ROLE_USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(auth -> auth
                .antMatchers("/super/**").hasRole("SUPER_ADMIN")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/staff/**").hasRole("STAFF")
                .antMatchers("/user/**").hasRole("USER")
                .anyRequest().authenticated()
            )
            .formLogin(withDefaults());
    }
}

问题:每个用户都需要配置多个角色,新增层级时所有用户数据都要修改。

操作后配置(使用 RoleHierarchy)

@Configuration
@EnableWebSecurity
public class HierarchyConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
        hierarchy.setHierarchy(
            "ROLE_SUPER_ADMIN > ROLE_ADMIN\n" +
            "ROLE_ADMIN > ROLE_STAFF\n" +
            "ROLE_STAFF > ROLE_USER"
        );
        return hierarchy;
    }

    // 关键:将 RoleHierarchy 注入到 AccessDecisionManager
    @Bean
    public AccessDecisionManager accessDecisionManager() {
        RoleHierarchyVoter hierarchyVoter = new RoleHierarchyVoter(roleHierarchy());
        List<AccessDecisionVoter<?>> voters = Arrays.asList(
            hierarchyVoter,  // 使用支持层级展开的 Voter
            new AuthenticatedVoter()
        );
        return new AffirmativeBased(voters);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("super")
                .password("{noop}super")
                .roles("SUPER_ADMIN")  // 只需配置一个角色!
            .and()
            .withUser("admin")
                .password("{noop}admin")
                .roles("ADMIN")
            .and()
            .withUser("staff")
                .password("{noop}staff")
                .roles("STAFF")
            .and()
            .withUser("user")
                .password("{noop}user")
                .roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(auth -> auth
                .accessDecisionManager(accessDecisionManager())
                .antMatchers("/super/**").hasRole("SUPER_ADMIN")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/staff/**").hasRole("STAFF")
                .antMatchers("/user/**").hasRole("USER")
                .anyRequest().authenticated()
            )
            .formLogin(withDefaults());
    }
}

结果分析

用户配置角色展开后权限可访问路径
superROLE_SUPER_ADMIN[ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_STAFF, ROLE_USER]/super/**, /admin/**, /staff/**, /user/**
adminROLE_ADMIN[ROLE_ADMIN, ROLE_STAFF, ROLE_USER]/admin/**, /staff/**, /user/**
staffROLE_STAFF[ROLE_STAFF, ROLE_USER]/staff/**, /user/**
userROLE_USER[ROLE_USER]/user/**

示例二:RoleHierarchy 在方法级别安全中的生效

场景说明

在 @PreAuthorize 注解中使用 hasRole() 时,RoleHierarchy 是否同样生效?验证方法级别安全的角色继承。

操作前配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodHierarchyConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
        hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
        return hierarchy;
    }

    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler = 
            new DefaultMethodSecurityExpressionHandler();
        handler.setRoleHierarchy(roleHierarchy());  // 关键:为方法安全注入层级
        return handler;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin").password("{noop}admin").roles("ADMIN")
            .and()
            .withUser("user").password("{noop}user").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests(auth -> auth.anyRequest().authenticated())
            .formLogin(withDefaults());
    }
}
@Service
public class DocumentService {

    @PreAuthorize("hasRole('USER')")
    public Document readDocument(Long id) {
        return documentRepository.findById(id).orElseThrow();
    }

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteDocument(Long id) {
        documentRepository.deleteById(id);
    }
}

结果分析

  • admin 用户调用 readDocument(1L):

    • @PreAuthorize("hasRole('USER')") 需要 ROLE_USER
    • admin 实际拥有 ROLE_ADMIN
    • RoleHierarchy 展开后:ROLE_ADMIN → 包含 ROLE_USER
    • DefaultMethodSecurityExpressionHandler 使用 RoleHierarchy 评估 → 通过
  • user 用户调用 deleteDocument(1L):

    • @PreAuthorize("hasRole('ADMIN')") 需要 ROLE_ADMIN
    • user 只有 ROLE_USER
    • 展开后仍不包含 ROLE_ADMIN → 403

关键点:方法级别安全必须显式将 RoleHierarchy 注入 MethodSecurityExpressionHandler,否则 hasRole() 在方法安全中不会自动展开继承。


易错场景:RoleHierarchy 在 URL 级别生效但在方法级别失效

场景:开发者配置了 RoleHierarchy Bean,发现 URL 级别的 hasRole("USER") 对 ADMIN 用户有效,但 @PreAuthorize("hasRole('USER')") 对 ADMIN 用户抛出 403。

原因:Spring Security 的 URL 级别授权和方法级别授权使用两套不同的 ExpressionHandler。RoleHierarchy 不会自动传播到方法安全表达式处理器。

错误配置:

@Bean
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
    return hierarchy;
}
// 错误:没有为方法安全配置 ExpressionHandler

正确配置:

@Bean
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
    return hierarchy;
}

// 关键:为方法安全配置 RoleHierarchy
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
    DefaultMethodSecurityExpressionHandler handler = 
        new DefaultMethodSecurityExpressionHandler();
    handler.setRoleHierarchy(roleHierarchy());
    return handler;
}

// 为 URL 级别配置 RoleHierarchy(Spring Security 5.x 方式)
@Bean
public AccessDecisionManager accessDecisionManager() {
    RoleHierarchyVoter hierarchyVoter = new RoleHierarchyVoter(roleHierarchy());
    List<AccessDecisionVoter<?>> voters = Arrays.asList(
        hierarchyVoter,
        new AuthenticatedVoter()
    );
    return new AffirmativeBased(voters);
}

面试考点:

问:配置了 RoleHierarchy 后,为什么 @PreAuthorize("hasRole('USER')") 对 ADMIN 用户不生效? 答:RoleHierarchy 默认只注入到 URL 级别的授权体系中(通过 RoleHierarchyVoter)。方法级别安全使用 MethodSecurityExpressionHandler 解析 SpEL 表达式,需要显式创建 DefaultMethodSecurityExpressionHandler 并调用 setRoleHierarchy(roleHierarchy()) 注入层级。URL 级别和方法级别是两个独立的授权管道,配置不会自动传播。


角色层级配置速查

配置位置需要注入 RoleHierarchy 的组件代码示例
URL 级别RoleHierarchyVoter + AccessDecisionManagernew RoleHierarchyVoter(roleHierarchy)
方法级别MethodSecurityExpressionHandlerhandler.setRoleHierarchy(roleHierarchy)
两者兼顾分别配置上述两套见上面正确配置
上一页
@Secured