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

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);

测试验证

  1. 重启应用,注册管理员用户(第一个注册用户)。
  2. 用管理员账号发送DELETE请求/api/auth/user/{username},验证是否成功。
  3. 用对应角色账号发送GET请求/api/auth/list,验证是否返回用户列表。
  4. 验证其他角色权限请求是否正常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 17:04:49