基于角色认证的REST API测试时出现Error 403问题
Spring Security角色认证403错误排查与解决
我开发了一个基于角色认证的简易REST API应用,使用Postman或Swagger测试接口时,即便使用拥有对应角色的用户完成登录,添加角色访问限制后仍会触发Error 403错误,仅当设置permitAll()时才能正常访问接口。登录操作可正常完成,但访问带角色限制的接口时返回Error 403。
相关代码
SecurityConfig类
@Configuration @EnableMethodSecurity public class SecurityConfig { private UserDetailsService userDetailsService; public SecurityConfig(UserDetailsService userDetailsService){ this.userDetailsService = userDetailsService; } @Bean public static PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf().disable() .cors().disable() .authorizeHttpRequests((authorize) -> authorize.requestMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN") .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/swagger-ui/**").permitAll() .requestMatchers("/v3/api-docs/**").permitAll() //.requestMatchers("/api/admin/**").hasRole("ADMIN") //.requestMatchers("/api/user/**").hasRole("USER") .anyRequest().authenticated() ); return http.build(); } }
AuthController类
@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserRepository userRepository; @Autowired private RoleRepository roleRepository; @Autowired private PasswordEncoder passwordEncoder; @PostMapping("/v1/signin") public ResponseEntity<String> authenticateUser(@RequestBody LoginDto loginDto){ Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( loginDto.getEmail(), loginDto.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); return new ResponseEntity<>("User signed-in successfully!.", HttpStatus.OK); } @PostMapping("/v1/signup") public ResponseEntity<?> registerUser(@RequestBody @Validated SignUpDto signUpDto){ if(userRepository.existsByEmail(signUpDto.getEmail())){ return new ResponseEntity<>("Email già in uso scegli una mail diversa!", HttpStatus.BAD_REQUEST); } User user = new User(); user.setName(signUpDto.getName()); user.setSurname(signUpDto.getSurname()); user.setEmail(signUpDto.getEmail()); user.setPassword(passwordEncoder.encode(signUpDto.getPassword())); user.setEnabled(true); Role roles = roleRepository.findByName("ROLE_USER").get(); user.setRoles(Collections.singleton(roles)); userRepository.save(user); return new ResponseEntity<>("Nuovo utente registrato correttamente!", HttpStatus.OK); } }
CustomUserDetailService类
@Service public class CustomUserDetailsService implements UserDetailsService { private UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException("User not found with email: "+ email)); Set<GrantedAuthority> authorities = user .getRoles() .stream() .map((role) -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toSet()); return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities); } }
User类
@Table(name = "User") public class User { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 20) private String name; @Column(nullable = false, length = 20) private String surname; @Column(nullable = false, unique = true, length = 45) private String email; @Column(nullable = false, length = 64) private String password; @Column(nullable = false, length = 1) private boolean enabled; @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) private Set<Role> roles; }
Role类
@Table(name = "roles") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @Column(length = 60) private String name; }
问题排查与解决
出现403的核心原因是会话未被正确维护。当前登录逻辑仅完成了认证,但请求之间未复用会话信息,导致访问受保护接口时Spring Security无法识别用户身份和权限。
解决步骤:
确保会话一致性
在Postman/Swagger测试时,必须启用Cookie保存功能,让后续请求自动携带登录时生成的JSESSIONIDCookie。如果每次请求都是全新的会话,Spring Security会判定为未认证状态,返回403。验证权限加载正确性
在CustomUserDetailsService的loadUserByUsername方法中添加日志,输出用户的权限集合,确认目标用户确实拥有ROLE_ADMIN权限:System.out.println("Loaded user authorities: " + authorities);权限配置匹配检查
- 若使用
hasRole("ADMIN"),需确保数据库中角色名称为ADMIN(不带ROLE_前缀),因为hasRole会自动添加前缀; - 若使用
hasAuthority("ROLE_ADMIN"),则数据库中角色名称必须完整带ROLE_前缀,与当前代码保持一致。
- 若使用
补充管理员角色分配逻辑
当前注册接口仅分配ROLE_USER角色,测试管理员接口前,需手动在数据库中给目标用户添加ROLE_ADMIN角色记录,或新增管理员专属注册接口。
内容的提问来源于stack exchange,提问作者epa
相关产品推荐
相关产品推荐

