Spring Security认证授权后仍出现403 Forbidden错误排查
问题分析及解决方案
核心问题根源
- User实体未实现UserDetails接口:Spring Security的
UserDetailsService返回的用户对象必须实现UserDetails接口,否则框架无法识别用户的权限信息,直接导致权限校验失败触发403。 - 角色权限前缀不匹配:
hasRole("ADMIN")会自动匹配带ROLE_前缀的权限字符串(如ROLE_ADMIN),若User类返回的权限是ADMIN(无前缀),则无法匹配校验规则。 - 重复创建默认用户:每次调用注册接口都会重新创建Admin和Applicant用户,可能导致数据库中存在多个同名用户,登录时无法获取正确权限的用户实例。
分步修复方案
1. 让User实体实现UserDetails接口
修改User类,实现UserDetails并正确重写权限相关方法,确保Spring Security能识别用户权限:
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import jakarta.persistence.*; import java.util.Collection; import java.util.List; @Entity public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; private String userName; private String passwordHash; @Enumerated(EnumType.STRING) private UserType role; // 其他字段:email、address、profileHeadline等 // 重写UserDetails核心方法 @Override public Collection<? extends GrantedAuthority> getAuthorities() { // 为角色添加ROLE_前缀,匹配hasRole的校验规则 return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); } @Override public String getPassword() { return passwordHash; // 返回加密后的密码 } @Override public String getUsername() { return userName; // 返回用户名 } @Override public boolean isAccountNonExpired() { return true; // 根据业务需求调整 } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } // getter、setter、构造方法省略 }
2. 修复默认用户初始化逻辑
移除注册接口中重复创建Admin/Applicant的代码,改为项目启动时仅初始化一次:
@Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; // 项目启动时初始化默认用户(仅执行一次) @PostConstruct public void initDefaultUsers() { // 检查Admin是否存在,不存在则创建 userRepository.findByUserName("Admin").ifPresentOrElse( user -> {}, () -> { User admin = new User(); admin.setUserName("Admin"); admin.setPasswordHash(passwordEncoder.encode("Admin")); admin.setRole(UserType.ADMIN); userRepository.save(admin); } ); // 检查Applicant是否存在,不存在则创建 userRepository.findByUserName("Applicant").ifPresentOrElse( user -> {}, () -> { User applicant = new User(); applicant.setUserName("Applicant"); applicant.setPasswordHash(passwordEncoder.encode("Applicant")); applicant.setRole(UserType.APPLICANT); userRepository.save(applicant); } ); } public User registerUser(User user) { // 仅处理新用户注册,加密密码后保存 user.setPasswordHash(passwordEncoder.encode(user.getPasswordHash())); return userRepository.save(user); } // 其他方法省略 }
3. 权限校验规则的可选调整(若无需ROLE_前缀)
如果不想在权限中添加ROLE_前缀,可将SecurityConfig中的hasRole改为hasAuthority:
// 原代码 .requestMatchers("/admin/**").hasRole("ADMIN") // 修改为 .requestMatchers("/admin/**").hasAuthority("ADMIN")
同时修改User类的getAuthorities方法:
@Override public Collection<? extends GrantedAuthority> getAuthorities() { return List.of(new SimpleGrantedAuthority(role.name())); }
验证测试
- 重启项目,确保默认Admin用户已正确初始化到数据库
- 使用Postman选择Basic Auth认证方式,输入用户名
Admin、密码Admin - 请求
/admin/applicants接口,确认返回200状态码及正确数据
内容的提问来源于stack exchange,提问作者Waleed Najam
相关产品推荐
相关产品推荐

