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

基于Spring Boot 3.0.1的REST API JWT角色认证实现问询

基于Spring 3.0.1实现JWT角色权限控制的REST API

问题分析

你当前的配置缺少JWT核心组件:没有令牌生成逻辑、没有请求时的JWT验证过滤器,且AuthenticationManager配置方式不符合Spring Security 3.x规范,导致开放端点也可能返回401,同时无法处理角色权限校验。

步骤1:添加必要依赖

在pom.xml中添加Spring Security和JWT相关依赖(Gradle项目对应调整):

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT 依赖 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

步骤2:修复UserDetailsService构造函数错误

你当前的MyUserDetailsService构造函数名称与类名不符,修改后如下:

@Service
public class MyUserDetailsService implements UserDetailsService {
    private final UserAuthService userAuthService;

    public MyUserDetailsService(UserAuthService userAuthService) {
        this.userAuthService = userAuthService;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<User> user = userAuthService.findUserByUsername(username);
        if (user.isEmpty()) {
            throw new UsernameNotFoundException("User not found");
        }
        // 给角色添加ROLE_前缀,符合Spring Security默认规则
        List<GrantedAuthority> authorities = Collections.singletonList(
                new SimpleGrantedAuthority("ROLE_" + user.get().getGroMedRole().toString())
        );
        return new org.springframework.security.core.userdetails.User(
                user.get().getUsername(),
                user.get().getPassword(),
                authorities
        );
    }
}

步骤3:创建JWT工具类

负责生成、解析、验证JWT令牌:

@Component
public class JwtTokenProvider {
    // 生产环境建议从配置文件读取,此处仅示例
    private final String JWT_SECRET = "your-32bit-or-longer-secure-secret-key";
    private final long JWT_EXPIRATION_MS = 86400000; // 24小时

    // 生成JWT令牌
    public String generateToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + JWT_EXPIRATION_MS);

        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, JWT_SECRET)
                .compact();
    }

    // 从令牌中提取用户名
    public String getUsernameFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(JWT_SECRET)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    // 验证令牌有效性
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token);
            return true;
        } catch (SignatureException | MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            // 可根据异常类型返回具体错误信息,此处仅返回false
            return false;
        }
    }
}

步骤4:实现JWT认证过滤器

拦截所有请求,从请求头提取JWT并验证,验证通过后将用户信息存入Security上下文:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    @Autowired
    private MyUserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = getJwtFromRequest(request);

        if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
            String username = jwtTokenProvider.getUsernameFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities()
            );
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

步骤5:实现登录接口

处理用户登录请求,验证成功后返回JWT令牌:

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @PostMapping("/login")
    public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) {
        // 验证用户名密码
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginRequest.getUsername(),
                        loginRequest.getPassword()
                )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtTokenProvider.generateToken(authentication);
        return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
    }

    @PostMapping("/register")
    public ResponseEntity<?> registerUser(@RequestBody RegisterRequest registerRequest) {
        // 实现注册逻辑:密码需用BCryptPasswordEncoder加密后存入数据库
        return ResponseEntity.ok("User registered successfully");
    }

    // 辅助DTO类
    public static class LoginRequest {
        private String username;
        private String password;
        // getter/setter
        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 static class JwtAuthenticationResponse {
        private String accessToken;
        public JwtAuthenticationResponse(String accessToken) { this.accessToken = accessToken; }
        public String getAccessToken() { return accessToken; }
        public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
    }

    public static class RegisterRequest {
        private String username;
        private String password;
        private GroMedRole role;
        // getter/setter
        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 GroMedRole getRole() { return role; }
        public void setRole(GroMedRole role) { this.role = role; }
    }
}

步骤6:完善SecurityConfig配置

修正AuthenticationManager配置,添加JWT过滤器,配置角色权限:

@EnableWebSecurity
@Configuration
public class SecurityConfig {
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;
    @Autowired
    private MyUserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/auth/**").permitAll()
                        // ADMIN角色专属端点
                        .requestMatchers("/api/admin/**").hasRole("ADMIN")
                        // USER/ADMIN均可访问的端点
                        .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
                        // 其余请求需认证
                        .anyRequest().authenticated()
                );

        // 将JWT过滤器添加到Security链中,放在用户名密码过滤器之前
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

关键说明

  1. 角色前缀:Spring Security默认要求角色以ROLE_为前缀,所以在MyUserDetailsService中给角色添加该前缀,确保hasRole方法能正确匹配。
  2. AuthenticationManager:Spring Security 3.x推荐通过AuthenticationConfiguration获取,而非手动构建。
  3. JWT密钥:生产环境必须从环境变量或加密配置文件读取,密钥长度至少32位以保证安全性。
  4. 令牌过期:可根据业务需求调整过期时间,复杂场景可添加刷新令牌机制。

测试流程

  1. 调用POST /api/auth/register注册用户,密码需用BCryptPasswordEncoder加密后存入数据库。
  2. 调用POST /api/auth/login获取JWT令牌。
  3. 访问受保护端点时,在请求头添加Authorization: Bearer <你的令牌>,验证不同角色的访问权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 23:45:41