SpringBoot角色认证问题:GET/DELETE请求无法通过认证求助
SpringBoot角色认证系统:GET/DELETE请求认证失败排查
我正在搭建SpringBoot基于角色的用户认证系统,目前POST请求可正常运行,但GET和DELETE请求无法通过认证。怀疑问题出在GrantedAuthority配置、Role枚举以及整体权限逻辑上,不清楚认证失败的具体原因,希望得到技术帮助。
用户实体类(User)
package antifraud.model; import antifraud.enums.Role; import antifraud.enums.UserStatus; import jakarta.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; //表示用户实体 @Entity @Table(name = "app_user") public class User implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String name; @Column(nullable = false, unique = true) private String username; @Column(nullable = false) private String password; @Enumerated(EnumType.STRING) private Role role; @Enumerated(EnumType.STRING) private UserStatus status; @Column(nullable = false) private boolean isAccountLocked; @ManyToMany private final List<RoleUser> roles = new ArrayList<>(); public Collection<RoleUser> getRoles() { return roles; } public User(String name, String username, String password, Role role, UserStatus status) { this.id = id; this.name = name; this.username = username; this.password = password; this.role = role; this.status = status; } public User() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public UserStatus getStatus() { return status; } public void setStatus(UserStatus status) { this.status = status; } public boolean isAccountLocked() { return isAccountLocked; } public void setAccountLocked(boolean accountNonLocked) { isAccountLocked = accountNonLocked; } }
安全配置类(SecurityConfig)
package antifraud.config; import antifraud.service.CustomUserDetailsService; import antifraud.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.servlet.PathRequest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.http.HttpMethod; import javax.management.relation.Role; //配置Spring Security @Configuration @EnableWebSecurity @EnableMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfig { @Autowired private CustomUserDetailsService customUserDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf().disable() .authorizeRequests() .requestMatchers(HttpMethod.POST, "/api/auth/user").permitAll() .requestMatchers(HttpMethod.DELETE, "/api/auth/user").hasRole("ADMINISTRATOR") .requestMatchers(HttpMethod.GET, "/api/auth/list").hasAnyRole("ADMINISTRATOR", "MERCHANT", "SUPPORT") .requestMatchers(HttpMethod.POST, "/api/antifraud/transaction").hasRole("MERCHANT") .requestMatchers(HttpMethod.PUT, "/api/auth/access").hasRole("ADMINISTRATOR") .requestMatchers(HttpMethod.PUT, "/api/auth/role").hasRole("ADMINISTRATOR") .anyRequest().authenticated() .and() .formLogin().disable() .httpBasic() .and() .build(); } }
认证控制器类(AuthController)
package antifraud.controller; import antifraud.dtos.UserDTO; import antifraud.dtos.UserRoleDto; import antifraud.dtos.UserStatusDto; import antifraud.enums.Role; import antifraud.enums.UserStatus; import antifraud.model.*; import antifraud.repository.UserRepository; import antifraud.exceptions.BadRequestException; import antifraud.exceptions.ConflictException; import antifraud.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; import javax.validation.Valid; import java.util.List; import java.util.stream.Collectors; //处理用户注册、列表查询和删除 @EnableMethodSecurity @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; @Autowired private UserService userService; @PostMapping("/user") public ResponseEntity<?> registerUser(@Valid @RequestBody User user) { if (user.getUsername() == null || user.getPassword() == null|| user.getName() == null) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } if (userRepository.findByUsernameIgnoreCase(user.getUsername()).isPresent()) { return ResponseEntity.status(HttpStatus.CONFLICT).body("User already exists"); } // 根据现有用户数量分配角色 Role role = userRepository.count() == 0 ? Role.ADMINISTRATOR : Role.MERCHANT; user.setRole(role); // 设置默认锁定状态 user.setAccountLocked(role == Role.ADMINISTRATOR ? false : true); // 加密密码并保存用户 user.setPassword(passwordEncoder.encode(user.getPassword())); User savedUser = userRepository.save(user); // 准备响应 UserDTO userDTO = new UserDTO(savedUser.getName(), savedUser.getUsername(), savedUser.getId(), savedUser.getRole()); return ResponseEntity.status(HttpStatus.CREATED).body(userDTO); } @PutMapping("/role") public ResponseEntity<User> changeUserRole(@RequestBody UserRoleDto userRoleDto) throws ConflictException { User user = userService.changeUserRole(userRoleDto.getUsername(), userRoleDto.getRole()); return new ResponseEntity<>(user, HttpStatus.OK); } @PutMapping("/access") public ResponseEntity<StatusResponse> changeUserStatus(@RequestBody UserStatusDto userStatusDto) throws BadRequestException { User user = userService.changeUserStatus(userStatusDto.getUsername(), UserStatus.valueOf(userStatusDto.getOperation())); return new ResponseEntity<>(new StatusResponse("User " + user.getUsername() + " " + user.getStatus().name().toLowerCase() + "!"), HttpStatus.OK); } @GetMapping("/list") @PreAuthorize("hasAnyRole('ADMINISTRATOR', 'MERCHANT', 'SUPPORT')") public ResponseEntity<?> listUsers(@RequestBody User user) throws ConflictException { if (user.getRole() != Role.ADMINISTRATOR || user.getRole() != Role.MERCHANT || user.getRole() != Role.SUPPORT) { throw new ResponseStatusException(HttpStatus.FORBIDDEN); } List<User> userss = userRepository.findAll(); // 转换为DTO(如有需要) List<UserDTO> userDTOs = userss.stream() .map(users -> new UserDTO(users.getName(), users.getUsername(), users.getId(), users.getRole())) .collect(Collectors.toList()); userss.forEach(users -> System.out.println("Id: " + users.getId() + "User: " + users.getName() + " - " + users.getUsername() + "Role: " + users.getRole())); return ResponseEntity.ok(userDTOs); } @DeleteMapping("/user/{username}") public ResponseEntity<?> deleteUser(@PathVariable String username) { User user = userRepository.findByUsernameIgnoreCase(username) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")); userRepository.delete(user); return ResponseEntity.ok().body("{\"username\": \"" + username + "\", \"status\": \"Deleted successfully!\"}"); } }
自定义用户详情服务类(CustomUserDetailsService)
package antifraud.service; import antifraud.model.User; import antifraud.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.Collection; import java.util.stream.Collectors; //从数据库加载用户详情用于认证 @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsernameIgnoreCase(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), getAuthorities(user) ); } private Collection<? extends GrantedAuthority> getAuthorities(User user) { return user.getRoles().stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) .collect(Collectors.toList()); } }
用户服务类(UserService)
package antifraud.service; import antifraud.exceptions.BadRequestException; import antifraud.exceptions.ConflictException; import antifraud.enums.Role; import antifraud.enums.UserStatus; import antifraud.model.User; import antifraud.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service public class UserService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsernameIgnoreCase(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); return org.springframework.security.core.userdetails.User.builder() .username(user.getUsername()) .password(user.getPassword()) .roles(user.getRole().name()) .disabled(user.getStatus() == UserStatus.LOCKED) .build(); } public User registerUser(String name, String username, String password) { Role role = userRepository.count() == 0 ? Role.ADMINISTRATOR : Role.MERCHANT; UserStatus status = role == Role.ADMINISTRATOR ? UserStatus.ACTIVE : UserStatus.LOCKED; User user = new User(name, username, password, role, status); return userRepository.save(user); } public User changeUserRole(String username, Role newRole) throws ConflictException { User user = userRepository.findByUsernameIgnoreCase(username).orElseThrow(() -> new UsernameNotFoundException("User not found")); if (user.getRole() == newRole) { throw new ConflictException("User already has the role " + newRole); } user.setRole(newRole); return userRepository.save(user); } public User changeUserStatus(String username, UserStatus newStatus) throws BadRequestException { User user = userRepository.findByUsernameIgnoreCase(username).orElseThrow(() -> new UsernameNotFoundException("User not found")); if (user.getRole() == Role.ADMINISTRATOR) { throw new BadRequestException("Cannot change status of ADMINISTRATOR"); } user.setStatus(newStatus); return userRepository.save(user); } }
核心问题排查与修复
1. 清理User实体类冗余角色配置
User类同时存在单个role字段和空的roles集合,业务逻辑实际只用单个角色,删除无用的roles集合及相关代码,避免权限加载时获取空集合。
2. 统一UserDetailsService实现
删除CustomUserDetailsService,保留UserService中的实现(该实现正确从user.getRole()加载角色),避免Spring加载冲突的用户详情服务。同时修改SecurityConfig,注入UserService并配置PasswordEncoder Bean。
3. 修正SecurityConfig路径匹配
DELETE请求路径为/api/auth/user/{username},原配置的/api/auth/user不匹配,修改为:
.requestMatchers(HttpMethod.DELETE, "/api/auth/user/{username}").hasRole("ADMINISTRATOR")
4. 修复GET请求控制器逻辑
GET请求不应携带@RequestBody,删除listUsers方法的@RequestBody User user参数及手动角色判断(@PreAuthorize已处理权限校验):
@GetMapping("/list") @PreAuthorize("hasAnyRole('ADMINISTRATOR', 'MERCHANT', 'SUPPORT')") public ResponseEntity<?> listUsers() { List<User> users = userRepository.findAll(); List<UserDTO> userDTOs = users.stream() .map(u -> new UserDTO(u.getName(), u.getUsername(), u.getId(), u.getRole())) .collect(Collectors.toList()); return ResponseEntity.ok(userDTOs); }
5. 同步用户状态与锁定字段
注册用户时,确保status字段与isAccountLocked同步:
UserStatus status = role == Role.ADMINISTRATOR ? UserStatus.ACTIVE : UserStatus.LOCKED; user.setStatus(status); user.setAccountLocked(status == UserStatus.LOCKED);
测试验证
- 重启应用,注册管理员用户(第一个注册用户)。
- 用管理员账号发送DELETE请求
/api/auth/user/{username},验证是否成功。 - 用对应角色账号发送GET请求
/api/auth/list,验证是否返回用户列表。 - 验证其他角色权限请求是否正常。
内容的提问来源于stack exchange,提问作者Carmen Montero
相关产品推荐
相关产品推荐

