You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot & Spring Security:混合角色与权限的访问控制异常问题

问题描述

使用Spring Boot 2.7.2配置Spring Security时,混合使用角色与权限后所有用户访问端点均被拒绝;但仅配置权限、移除角色配置时功能正常(bill可访问,john不可访问)。

有问题的配置:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            .httpBasic()
            .and()
            .authorizeRequests()
            .anyRequest().access("isAuthenticated() and hasAuthority('read')")
            .and()
            .build();
}

@Bean
public UserDetailsService userDetailsService(){
    InMemoryUserDetailsManager udm = new InMemoryUserDetailsManager();
    UserDetails ud = User.withUsername("bill")
            .password(passwordEncoder().encode("password"))
            .authorities("read")
            .roles("MANAGER")
            .build();

    UserDetails ud1 = User.withUsername("john")
            .password(passwordEncoder().encode("password"))
            .authorities("write")
            .roles("ADMIN")
            .build();

    udm.createUser(ud);
    udm.createUser(ud1);

    return udm;
}

正常工作的配置:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            .httpBasic()
            .and()
            .authorizeRequests()
            .anyRequest().access("isAuthenticated() and hasAuthority('read')")
            .and()
            .build();
}

@Bean
public UserDetailsService userDetailsService(){
    InMemoryUserDetailsManager udm = new InMemoryUserDetailsManager();
    UserDetails ud = User.withUsername("bill")
            .password(passwordEncoder().encode("password"))
            .authorities("read")
            .build();

    UserDetails ud1 = User.withUsername("john")
            .password(passwordEncoder().encode("password"))
            .authorities("write")
            .build();

    udm.createUser(ud);
    udm.createUser(ud1);

    return udm;
}
问题原因

Spring Security的User.Builder中,.roles()方法的逻辑是覆盖而非追加已有的权限集合,并且会自动给角色名添加ROLE_前缀。

当你先调用.authorities("read")再调用.roles("MANAGER")时,.roles()会清空之前设置的read权限,替换为ROLE_MANAGER;同理john的权限会变成ROLE_ADMIN,而非保留write。这就导致两个用户都没有read权限,触发访问控制规则的拒绝逻辑。

解决方案

有两种可行的方式实现角色与权限的共存:

方式1:调整调用顺序,先设置角色再追加权限

.roles()之后调用.authorities()时,会在角色对应的权限基础上追加新的权限,不会覆盖:

@Bean
public UserDetailsService userDetailsService(){
    InMemoryUserDetailsManager udm = new InMemoryUserDetailsManager();
    UserDetails ud = User.withUsername("bill")
            .password(passwordEncoder().encode("password"))
            .roles("MANAGER") // 先设置角色,自动添加ROLE_前缀
            .authorities("read") // 追加read权限
            .build();

    UserDetails ud1 = User.withUsername("john")
            .password(passwordEncoder().encode("password"))
            .roles("ADMIN")
            .authorities("write")
            .build();

    udm.createUser(ud);
    udm.createUser(ud1);

    return udm;
}

方式2:统一使用.authorities(),手动添加角色前缀

直接用.authorities()同时指定权限和带ROLE_前缀的角色,避免方法调用顺序的影响:

@Bean
public UserDetailsService userDetailsService(){
    InMemoryUserDetailsManager udm = new InMemoryUserDetailsManager();
    UserDetails ud = User.withUsername("bill")
            .password(passwordEncoder().encode("password"))
            .authorities("read", "ROLE_MANAGER") // 同时指定权限和角色(需加ROLE_前缀)
            .build();

    UserDetails ud1 = User.withUsername("john")
            .password(passwordEncoder().encode("password"))
            .authorities("write", "ROLE_ADMIN")
            .build();

    udm.createUser(ud);
    udm.createUser(ud1);

    return udm;
}

这两种方式都能保证用户同时拥有指定的角色和权限,满足hasAuthority('read')的访问控制要求。

内容的提问来源于stack exchange,提问作者Nathan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 13:25:17