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
相关产品推荐
相关产品推荐

