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

