如何实现共享密钥版NimbusJwtDecoder与NimbusJwtEncoder
问题描述
我尝试使用 org.springframework.security.oauth2.jwt 包提供的编码器/解码器,实现基于共享密钥的JWT处理方案,但在编码令牌时抛出 JwtEncodingException 异常,导致实现失败。此前曾以其他形式提出过该问题,本次提供可直接运行的简易示例用于复现问题:
import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.source.ImmutableSecret; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.springframework.security.oauth2.jwt.*; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; public class EncoderDecoderTest { public static String secret = "j8IoV1jF67"; public JwtEncoder jwtEncoder() throws JOSEException { SecretKey originalKey = new SecretKeySpec(secret.getBytes(), "AES"); JWKSource<SecurityContext> immutableSecret = new ImmutableSecret<SecurityContext>(originalKey); return new NimbusJwtEncoder(immutableSecret); } public JwtDecoder jwtDecoder() { SecretKey originalKey = new SecretKeySpec(secret.getBytes(), "AES"); NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(originalKey).build(); return jwtDecoder; } public void tester() throws JOSEException { JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("self") //Only this for simplicity .build(); var encoder = jwtEncoder(); //This line throws the exception String token = encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); System.out.println(token); var decoder = jwtDecoder(); Jwt jwt = decoder.decode(token); System.out.println(jwt.getIssuer()); } public static void main(String[] args) throws JOSEException { new EncoderDecoderTest().tester(); } }
经排查根因为AES不属于合法的JWT签名算法,现需要实现基于共享密钥、使用HMAC-SHA256算法的NimbusJwtDecoder与NimbusJwtEncoder配置,多次尝试未成功,需要可行的修复方案。
问题根因
代码存在三个核心问题:
- 密钥算法类型错误:AES是对称加密算法,不属于JWT支持的签名算法。HMAC系列JWT签名要求密钥算法使用
HmacSHA256(对应HS256)、HmacSHA384(对应HS384)、HmacSHA512(对应HS512)。 - 密钥长度不达标:HS256算法要求密钥长度至少为256位(即32字节),示例中使用的密钥仅10字节,不满足算法安全要求,会触发校验异常。
- 编码时未指定签名算法:
NimbusJwtEncoder无法自动推断签名算法,需要在编码参数中显式指定JWS头部的算法标识,否则无法完成签名逻辑。
修复方案
1. 准备符合要求的密钥
HS256要求密钥长度≥32字节,HS384≥48字节,HS512≥64字节,生产环境请从安全配置源(环境变量、配置中心)读取密钥,禁止硬编码。示例使用32字节测试密钥:
// 测试用32字节密钥,满足HS256最低长度要求 public static final String SECRET = "j8IoV1jF67abcdefghijklmnopqrstuvw";
2. 修正编码器配置
将密钥算法替换为HmacSHA256,密钥字符集指定为UTF-8避免编码不一致问题:
public JwtEncoder jwtEncoder() { SecretKey secretKey = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); JWKSource<SecurityContext> jwkSource = new ImmutableSecret<>(secretKey); return new NimbusJwtEncoder(jwkSource); }
3. 修正解码器配置
显式指定解码器使用的MAC算法为HS256,避免算法混淆攻击:
public JwtDecoder jwtDecoder() { SecretKey secretKey = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); return NimbusJwtDecoder.withSecretKey(secretKey) .macAlgorithm(MacAlgorithm.HS256) .build(); }
4. 修正令牌生成逻辑
编码令牌时传入指定HS256算法的JWS头部:
public void tester() { JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("self") .build(); // 显式指定JWS签名算法为HS256 JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256).build(); JwtEncoder encoder = jwtEncoder(); String token = encoder.encode(JwtEncoderParameters.from(jwsHeader, claims)).getTokenValue(); System.out.println("生成的Token:" + token); JwtDecoder decoder = jwtDecoder(); Jwt jwt = decoder.decode(token); System.out.println("解析得到的签发方:" + jwt.getIssuer()); }
完整可运行代码
需要额外引入java.nio.charset.StandardCharsets依赖:
import com.nimbusds.jose.jwk.source.ImmutableSecret; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.springframework.security.oauth2.jwt.*; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; public class EncoderDecoderTest { public static final String SECRET = "j8IoV1jF67abcdefghijklmnopqrstuvw"; public JwtEncoder jwtEncoder() { SecretKey secretKey = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); JWKSource<SecurityContext> jwkSource = new ImmutableSecret<>(secretKey); return new NimbusJwtEncoder(jwkSource); } public JwtDecoder jwtDecoder() { SecretKey secretKey = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); return NimbusJwtDecoder.withSecretKey(secretKey) .macAlgorithm(MacAlgorithm.HS256) .build(); } public void tester() { JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("self") .build(); JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256).build(); JwtEncoder encoder = jwtEncoder(); String token = encoder.encode(JwtEncoderParameters.from(jwsHeader, claims)).getTokenValue(); System.out.println("生成的Token:" + token); JwtDecoder decoder = jwtDecoder(); Jwt jwt = decoder.decode(token); System.out.println("解析得到的签发方:" + jwt.getIssuer()); } public static void main(String[] args) { new EncoderDecoderTest().tester(); } }
内容的提问来源于stack exchange,提问作者Plaul
相关产品推荐
相关产品推荐

