
本文详解如何使用 spring security 的 jwtdecoder 将原始 jwt 字符串安全解码,并构建标准的 jwtauthenticationtoken 实例,涵盖依赖配置、解码逻辑、权限提取及常见注意事项。
本文详解如何使用 spring security 的 jwtdecoder 将原始 jwt 字符串安全解码,并构建标准的 jwtauthenticationtoken 实例,涵盖依赖配置、解码逻辑、权限提取及常见注意事项。
在 Spring Security 5.6+(尤其是 OAuth2 Resource Server 场景)中,JwtAuthenticationToken 是用于承载已验证 JWT 主体信息的标准认证对象。但需注意:不能直接将原始 JWT 字符串“构造”为 JwtAuthenticationToken——必须先通过合法的 JwtDecoder 进行签名验证、解析和声明校验,再基于解析后的 Jwt 对象手动组装。
✅ 正确实现步骤
-
引入必要依赖(Maven):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <!-- 若需自定义解码器(如无公钥/密钥配置时临时测试),可显式引入 Nimbus --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency>
使用 NimbusJwtDecoder 解码(仅限开发/测试场景)
⚠️ 注意:new NimbusJwtDecoder() 不配置任何密钥或公钥,无法验证签名,生产环境严禁使用! 它仅适用于本地调试或已知可信令牌的单元测试。生产环境必须配置有效的 JWK Set URI 或对称/非对称密钥。
以下为安全且可扩展的参考实现:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class JwtUtils {
// ✅ 生产环境应注入由 @Bean 配置的 JwtDecoder(如 JwtDecoders.fromIssuerLocation(...))
private static final JwtDecoder DECODER = NimbusJwtDecoder.withSecretKey(
javax.crypto.spec.SecretKeySpec("your-256-bit-secret-key-here".getBytes(), "HmacSHA256")
).build();
/**
* 将 JWT 字符串转换为 JwtAuthenticationToken(兼容 Spring Security 6.x)
* 注意:返回的是 UsernamePasswordAuthenticationToken —— Spring Security 6+ 中 JwtAuthenticationToken
* 已被标记为内部类,推荐使用其父类或直接构建 JwtAuthenticationToken(需 spring-security-oauth2-resource-server 6.2+)
*/
public static Authentication parseJwtToAuthentication(String jwtToken) throws JwtException {
if (!StringUtils.hasText(jwtToken)) {
throw new IllegalArgumentException("JWT token cannot be null or empty");
}
// Step 1: Decode & validate (signature, exp, iat, etc.)
Jwt jwt = DECODER.decode(jwtToken);
// Step 2: Extract principal (e.g., subject)
String username = jwt.getSubject();
if (username == null || username.trim().isEmpty()) {
throw new JwtException("JWT 'sub' claim is missing or empty");
}
// Step 3: Extract authorities — prefer standard 'scope' or custom 'authorities' claim
List<String> authorityStrings = getAuthorityStrings(jwt);
Collection<GrantedAuthority> authorities = authorityStrings.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// ✅ 返回标准 JwtAuthenticationToken(Spring Security 6.2+ 推荐)
// 注意:需确保 spring-security-oauth2-resource-server >= 6.2
return new org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken(
jwt, authorities
);
}
private static List<String> getAuthorityStrings(Jwt jwt) {
// 优先尝试标准 scope claim(空格分隔)
String scopes = jwt.getClaimAsString("scope");
if (StringUtils.hasText(scopes)) {
return List.of(scopes.split("\s+"));
}
// 回退到自定义 authorities 数组(JSON array of strings)
Object raw = jwt.getClaim("authorities");
if (raw instanceof String[]) {
return List.of((String[]) raw);
}
return Collections.emptyList();
}
}⚠️ 关键注意事项
- 绝不硬编码密钥或跳过验证:new NimbusJwtDecoder() 无密钥时会抛异常;若强行绕过验证(如反射修改),将导致严重安全漏洞。
- JwtAuthenticationToken 构造要求:Spring Security 6.2+ 中该类公开可用,但需传入 Jwt 实例与 Collection<GrantedAuthority>;其 getPrincipal() 返回 Jwt,getCredentials() 恒为 null。
- 权限字段命名规范:建议使用标准 scope(RFC 8693)而非私有 authorities,以提升互操作性。
- 异常处理:JwtException 及子类(如 JwtValidationException)需捕获并映射为合适的 HTTP 响应(如 401/403)。
✅ 总结
将 JWT 字符串转为认证对象的核心是:先解码验证,再结构化封装。始终依赖 Spring Security 官方 JwtDecoder 实现,避免手动解析 Base64 或忽略签名校验。生产环境务必通过 application.yml 配置 spring.security.oauth2.resourceserver.jwt.* 属性,或通过 @Bean 注册带密钥/公钥的解码器,确保端到端安全性与合规性。

















