You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于角色认证的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无法识别用户身份和权限。

解决步骤:

  1. 确保会话一致性
    在Postman/Swagger测试时,必须启用Cookie保存功能,让后续请求自动携带登录时生成的JSESSIONID Cookie。如果每次请求都是全新的会话,Spring Security会判定为未认证状态,返回403。

  2. 验证权限加载正确性
    在CustomUserDetailsService的loadUserByUsername方法中添加日志,输出用户的权限集合,确认目标用户确实拥有ROLE_ADMIN权限:

    System.out.println("Loaded user authorities: " + authorities);
    
  3. 权限配置匹配检查

    • 若使用hasRole("ADMIN"),需确保数据库中角色名称为ADMIN(不带ROLE_前缀),因为hasRole会自动添加前缀;
    • 若使用hasAuthority("ROLE_ADMIN"),则数据库中角色名称必须完整带ROLE_前缀,与当前代码保持一致。
  4. 补充管理员角色分配逻辑
    当前注册接口仅分配ROLE_USER角色,测试管理员接口前,需手动在数据库中给目标用户添加ROLE_ADMIN角色记录,或新增管理员专属注册接口。

内容的提问来源于stack exchange,提问作者epa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 11:34:52