Spring Security权限控制异常:/admin路径403 Forbidden求助
问题描述
尝试将/admin/**路径限制为仅ADMIN角色可访问,但访问时出现403 Forbidden错误,错误信息如下:
此应用未为/error配置显式映射,因此您看到的是回退页面。
Mon Apr 08 01:49:02 EDT 2024
发生意外错误(类型=Forbidden,状态=403)。
安全配置代码
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfiguration { private final UserService userService; @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/").permitAll() .requestMatchers("/login**", "/error**", "/resource/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .formLogin(withDefaults()) .oauth2Login((userInfo) -> userInfo .userInfoEndpoint((config) -> config.oidcUserService(userService)) ) .build(); } }
User实体代码
@Entity @Table(name = "user") @Data @NoArgsConstructor public class UserEntity implements Serializable, UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(unique = true) private String username; @Column private String password; @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE) @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) private Set<Role> roles = new HashSet<>(); @Override public Collection<? extends GrantedAuthority> getAuthorities() { return getRoles(); } }
Role实体代码
@Entity @Table(name = "role") @AllArgsConstructor @NoArgsConstructor @Getter @Setter public class Role implements GrantedAuthority { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(unique = true) private String name; @Override public String getAuthority() { return name; } }
UserService代码
@Service @AllArgsConstructor public class UserService extends OidcUserService { private final UserRepository userRepository; private final RoleRepository roleRepository; @Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { OidcUser oidcUser = super.loadUser(userRequest); try { return processOidcUser(userRequest, oidcUser); } catch (Exception e) { throw new InternalAuthenticationServiceException(e.getMessage(), e.getCause()); } } private OidcUser processOidcUser(OidcUserRequest userRequest, OidcUser oidcUser) { GoogleUserInfo googleUserInfo = new GoogleUserInfo(oidcUser.getAttributes()); Optional<UserEntity> userOptional = userRepository.findByUsername(googleUserInfo.getEmail()); if (userOptional.isEmpty()) { UserEntity user = new UserEntity(); user.setUsername(googleUserInfo.getEmail()); user.setPassword(googleUserInfo.getName()); Role role = roleRepository.findByName("ADMIN"); user.getRoles().add(role); userRepository.save(user); } return oidcUser; } }
已尝试以下配置但均无效:
.requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/admin/**").hasAuthority("ROLE_ADMIN") .requestMatchers("/admin/**").hasAuthority("ADMIN")
解决方案
核心问题:OAuth2登录后返回的原生OidcUser对象,并没有携带你数据库中存储的ADMIN角色权限,导致Spring Security无法识别用户的角色。
1. 自定义OidcUser实现类
创建自定义类,整合原生Oidc信息与数据库角色权限:
public class CustomOidcUser implements OidcUser { private final OidcUser oidcUser; private final Collection<? extends GrantedAuthority> authorities; public CustomOidcUser(OidcUser oidcUser, Collection<? extends GrantedAuthority> authorities) { this.oidcUser = oidcUser; this.authorities = authorities; } @Override public Map<String, Object> getAttributes() { return oidcUser.getAttributes(); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getName() { return oidcUser.getName(); } @Override public Map<String, Object> getClaims() { return oidcUser.getClaims(); } @Override public OidcIdToken getIdToken() { return oidcUser.getIdToken(); } @Override public OidcUserInfo getUserInfo() { return oidcUser.getUserInfo(); } }
2. 修改UserService的processOidcUser方法
不再返回原生OidcUser,而是返回携带数据库角色的自定义对象:
private OidcUser processOidcUser(OidcUserRequest userRequest, OidcUser oidcUser) { GoogleUserInfo googleUserInfo = new GoogleUserInfo(oidcUser.getAttributes()); Optional<UserEntity> userOptional = userRepository.findByUsername(googleUserInfo.getEmail()); UserEntity user; if (userOptional.isEmpty()) { user = new UserEntity(); user.setUsername(googleUserInfo.getEmail()); user.setPassword(googleUserInfo.getName()); Role role = roleRepository.findByName("ADMIN"); user.getRoles().add(role); userRepository.save(user); } else { user = userOptional.get(); } // 返回携带数据库角色的自定义OidcUser return new CustomOidcUser(oidcUser, user.getAuthorities()); }
3. 匹配角色权限格式
Spring Security的hasRole方法会自动给角色名称加上ROLE_前缀,所以需要二选一:
- 如果数据库中角色名称是
ADMIN,则安全配置改用.hasAuthority("ADMIN") - 如果想用
.hasRole("ADMIN"),则数据库中角色名称需改为ROLE_ADMIN
4. 验证权限加载情况
添加调试接口确认权限是否正确注入:
@GetMapping("/user/authorities") @ResponseBody public Collection<? extends GrantedAuthority> getCurrentUserAuthorities(Authentication authentication) { return authentication.getAuthorities(); }
访问该接口,查看返回结果中是否包含对应角色权限。
内容的提问来源于stack exchange,提问作者user24095161
相关产品推荐
相关产品推荐

