基于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(); } }
关键说明
- 角色前缀:Spring Security默认要求角色以
ROLE_为前缀,所以在MyUserDetailsService中给角色添加该前缀,确保hasRole方法能正确匹配。 - AuthenticationManager:Spring Security 3.x推荐通过
AuthenticationConfiguration获取,而非手动构建。 - JWT密钥:生产环境必须从环境变量或加密配置文件读取,密钥长度至少32位以保证安全性。
- 令牌过期:可根据业务需求调整过期时间,复杂场景可添加刷新令牌机制。
测试流程
- 调用
POST /api/auth/register注册用户,密码需用BCryptPasswordEncoder加密后存入数据库。 - 调用
POST /api/auth/login获取JWT令牌。 - 访问受保护端点时,在请求头添加
Authorization: Bearer <你的令牌>,验证不同角色的访问权限。
内容的提问来源于stack exchange,提问作者MrSolarius
相关产品推荐
相关产品推荐

