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

Spring Security角色权限异常:共用UserBuilder致角色叠加

问题:Spring Security角色叠加导致权限控制失效

场景与配置

配置Spring Security以限制仅拥有CARD-OWNER角色的用户访问/cashcards/**端点,核心配置代码如下:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
                                .requestMatchers("/cashcards/**")
                                .hasRole("CARD-OWNER")
                )
                .csrf(csrf -> csrf.disable())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    public UserDetailsService testOnlyUsers(PasswordEncoder passwordEncoder) {
        User.UserBuilder users = User.builder();
        UserDetails sarah = users
                .username("sarah1")
                .password(passwordEncoder.encode("abc123"))
                .roles("CARD-OWNER") // new role
                .build();

        UserDetails hankOwnsNoCards = users
                .username("hank-owns-no-cards")
                .password(passwordEncoder.encode("qrs456"))
                .roles("NO-OWNER") // new role
                .build();
        return new InMemoryUserDetailsManager(sarah, hankOwnsNoCards);

    }
}

对应的测试类:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class CashCardApplicationTests {
    @Autowired
    TestRestTemplate restTemplate;

    @Test
    void shouldRejectUsersWhoAreNotCardOwners() {
        ResponseEntity<String> response = restTemplate
                .withBasicAuth("hank-owns-no-cards", "qrs456")
                .getForEntity("/cashcards/99", String.class);
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
    }
}

问题现象

运行测试时,本应被拒绝的无权限用户(hank-owns-no-cards,角色为NO-OWNER)请求/cashcards/99却返回200 OK,而非预期的403 FORBIDDEN,关键错误信息如下:

expected: 403 FORBIDDEN
 but was: 200 OK
org.opentest4j.AssertionFailedError: 
expected: 403 FORBIDDEN
 but was: 200 OK
    at java.base@17.0.7/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base@17.0.7/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
    ...(省略中间堆栈信息)
CashCardApplicationTests > shouldRejectUsersWhoAreNotCardOwners() FAILED
    org.opentest4j.AssertionFailedError at CashcardApplicationTests.java:28

排查发现

当两个用户都设置为NO-OWNER角色时,测试正常通过。进一步检查用户权限信息发现,共用同一个User.UserBuilder实例创建用户时,第一个用户的角色会被叠加到后续用户中:

org.springframework.security.core.userdetails.User [Username=sarah1, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_CARD-OWNER]]
org.springframework.security.core.userdetails.User [Username=hank-owns-no-cards, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_CARD-OWNER, ROLE_NO-OWNER]]

可以看到,hank-owns-no-cards用户意外拥有了ROLE_CARD-OWNER角色,因此能成功访问受保护端点。

解决方案

为每个用户创建独立的User.UserBuilder实例,避免角色叠加。修正后的UserDetailsService配置如下:

@Bean
public UserDetailsService testOnlyUsers(PasswordEncoder passwordEncoder) {
    UserDetails sarah = User.builder()
            .username("sarah1")
            .password(passwordEncoder.encode("abc123"))
            .roles("CARD-OWNER") // new role
            .build();
    UserDetails hankOwnsNoCards = User.builder()
            .username("hank-owns-no-cards")
            .password(passwordEncoder.encode("qrs456"))
            .roles("NO-OWNER") // new role
            .build();
    
    return new InMemoryUserDetailsManager(sarah, hankOwnsNoCards);
}

修改后,每个用户的角色独立,测试可正常通过,无权限用户会被正确拦截返回403 FORBIDDEN。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 21:27:00