关于 spring-security-jwt
面向 Spring 开发者的 JWT 安全技能,提供实现模板与无状态 Bearer 认证、账户状态检查和令牌处理指南,帮助规避常见配置问题。
主要功能
- 01JWT 服务与认证过滤器模板
- 02无状态 Bearer 认证配置
- 03角色与方法级授权
- 04JSON 格式的 401 和 403 错误处理
- 05访问令牌与刷新令牌指南
适用场景
- 01为 Spring API 添加 JWT Bearer 认证
- 02在令牌认证时检查账户状态
- 03为端点和方法配置基于角色的访问控制
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rrezartprebreza/ spring-boot-skills --skill spring-security-jwt安装后在 Claude Code 中输入/spring-security-jwt确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlexamples/bad-security-config.javagood-security-config.javatemplates/JwtAuthenticationFilter.javaJwtService.javaSecurityConfig.java详细介绍
Spring Security — JWT
依赖项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
安全配置
使用编译后的 SecurityConfig,并配合下面的过滤器和服务。 它使用 lambda DSL、无状态 Bearer 身份验证、角色规则、JSON 401/403 处理器,并禁用安全链过滤器的 servlet 注册。Bean 方法注入可避免配置类与其自身的 AuthenticationProvider Bean 之间出现构造函数循环依赖。 请根据项目调整路由和角色。禁用 CSRF 仅适用于只通过请求头传递 Bearer 令牌的 API;如果浏览器会自动发送身份验证 Cookie,则应保留 CSRF 防护。
JWT 实现
配合使用经过测试的 JwtService 和
JwtAuthenticationFilter 模板。
配置项为 app.jwt.secret、app.jwt.access-token-expiration 和
app.jwt.refresh-token-expiration(时长单位为毫秒)。
过滤器会拒绝已过期、格式错误、遭篡改、缺少过期时间以及刷新令牌,并返回 401 和 Bearer 挑战。在身份验证前,它会检查当前用户是否已启用、已锁定、账户是否过期以及凭据是否过期。已删除的用户也会收到 401。 数据库中断和下游应用故障必须作为服务器故障处理,不能将其伪装成无效凭据。即使在公开端点上,也会拒绝提供的无效令牌。
过滤器示例中的错误响应正文使用 Problem Details。对于已有的旧版 API,请根据既定错误契约调整此响应和下面的入口点。切勿记录 Bearer 令牌。
只在安全链中注册过滤器 Bean:使用 FilterRegistrationBean<JwtAuthenticationFilter>,并将其 enabled 标志设为 false,以禁用 servlet 容器注册。
这些模板展示的是单服务、第一方令牌契约。在跨服务共享签名密钥或接受其他服务的令牌之前,请定义并验证签发者和受众、密钥轮换以及吊销机制。Spring Security 的资源服务器支持也可以验证自定义 JWT;如果它已适用于应用,请保留该支持。令牌生成并不构成完整的刷新流程:仍需满足下文的轮换和重用检测要求。
JSON 401/403
配置和过滤器模板对身份验证及授权错误使用 Problem Details。对于旧版错误契约,请同时调整两者。访问受保护路由时缺少凭据将返回 401;已通过身份验证但不具备所需角色的调用者将返回 403。即使路由允许匿名访问,提供的无效令牌仍会返回 401。 Controller advice 无法处理在 dispatcher servlet 之前抛出的异常。
Auth Controller
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ApiResponse<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
return ApiResponse.ok(authService.login(request));
}
@PostMapping("/refresh")
public ApiResponse<AuthResponse> refresh(@Valid @RequestBody RefreshRequest request) {
return ApiResponse.ok(authService.refresh(request.refreshToken()));
}
@PostMapping("/register")
public ResponseEntity<ApiResponse<AuthResponse>> register(@Valid @RequestBody RegisterRequest request) {
return ResponseEntity.status(201).body(ApiResponse.ok(authService.register(request)));
}
}
public record AuthResponse(String accessToken, String refreshToken, long expiresIn) {}
方法级安全
// On service methods
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(UUID userId) { ... }
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public UserProfile getProfile(UUID userId) { ... }
@PostAuthorize("returnObject.email == authentication.name")
public User findById(UUID id) { ... }
application.yml
app:
jwt:
secret: ${JWT_SECRET} # min 256-bit base64 encoded key
access-token-expiration: 900000 # 15 minutes
refresh-token-expiration: 604800000 # 7 days
示例会创建刷新令牌,但没有实现刷新端点。生产级刷新流程必须只接受 type=refresh 的令牌,每次使用时都轮换刷新令牌,并吊销之前的令牌(例如,在数据库或 Redis 中使用经过哈希处理的令牌族记录)。
易踩问题
- Agent 在用户查询周围笼统地捕获
AuthenticationException——应保留AuthenticationServiceException,将其视为服务器故障。 - Agent 会让持有有效 JWT 的禁用或锁定用户登录——除了校验声明,还要检查当前账户状态。
- Agent 使用旧版 API
HttpSecurity.csrf().disable()——应使用AbstractHttpConfigurer::disable - Agent 让
ExpiredJwtException从过滤器中逸出——过期令牌会导致返回 500,而不是 401;应在过滤器中捕获 - Agent 跳过
exceptionHandling()——客户端会收到空的 401/403 响应正文(或被重定向到登录页);@RestControllerAdvice无法捕获过滤器级别的异常 - Agent 将 JWT 密钥存储在代码中——始终从环境变量读取
${JWT_SECRET}(HS256 需要 ≥256 位的密钥,否则Keys.hmacShaKeyFor会抛出WeakKeyException) - Agent 使用
SessionCreationPolicy.IF_REQUIRED——JWT 必须使用STATELESS - Agent 只校验签名和过期时间——Bearer 过滤器必须只接受
type=access令牌;刷新令牌应由单独的刷新端点处理 - Agent 忘记启用
@EnableMethodSecurity,导致@PreAuthorize无法生效 - Agent 使用强度 < 10 的 BCrypt——生产环境应使用 12
- Agent 将令牌校验逻辑放在控制器中——这部分逻辑应放在过滤器中
- Agent 在示例中将刷新令牌放入 localStorage——应推荐 httpOnly Cookie 或安全存储;刷新令牌是长期有效的凭据