使用Spring OAuth2资源服务器和对称密钥如何避免KeyLengthException
问题背景
正在开发基于Spring Boot的资源服务器,需要使用Spring Security OAuth2 Resource Server库的能力。当前遇到的核心限制是:授权服务器为另一款Spring Boot应用,使用早年设置且无法修改的短对称密钥对JWT做HS512签名。
初始配置与密钥长度错误
参照Spring Security官方文档的初始配置如下:
@EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorize -> authorize .anyRequest().permitAll()) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); } @Bean public JwtDecoder jwtDecoder(@Value("${jwt.secret-key}") String secretKey) { return NimbusJwtDecoder .withSecretKey(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HS512")) .macAlgorithm(MacAlgorithm.HS512) .build(); } }
运行后抛出密钥长度不足的异常:
Caused by: com.nimbusds.jose.KeyLengthException: The secret length must be at least 256 bits at com.nimbusds.jose.crypto.impl.MACProvider.<init>(MACProvider.java:118) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1] ... 61 common frames omitted
抛出异常的MACProvider不支持自定义配置,无法直接放宽密钥长度要求。
首次修复尝试与签名校验错误
尝试对密钥做补0处理满足长度要求,配置如下:
@Bean public JwtDecoder jwtDecoder(@Value("${jwt.secret-key}") String secretKey) { var key = secretKey.getBytes(StandardCharsets.UTF_8); var paddedKey = Arrays.copyOf(key, 128); return NimbusJwtDecoder .withSecretKey(new SecretKeySpec(paddedKey, "HS512")) .macAlgorithm(MacAlgorithm.HS512) .build(); }
修改后抛出签名无效异常:
com.nimbusds.jose.proc.BadJWSException: Signed JWT rejected: Invalid signature ... 省略异常栈
根因确认
通过排查授权服务器的JWT生成逻辑,确认其使用io.jsonwebtoken 0.9.1版本库实现签名,生成代码如下:
private String generateToken(Map<String, Object> claims, String username) { Header header = Jwts.header(); header.setType("JWT"); String jti = UUID.randomUUID().toString(); Date now = new Date(System.currentTimeMillis()); return Jwts.builder() .setClaims(claims) .setHeader((Map<String, Object>) header) .setSubject(username) .setIssuedAt(now) .setIssuer("issuer") .setId(jti) .signWith(SignatureAlgorithm.HS512, secret) .compact(); }
旧版本jjwt库的signWith方法接收字符串类型密钥时,会默认先对字符串做BASE64解码,再将解码后的字节数组作为实际签名密钥。而资源服务器初始直接将密钥字符串转为UTF-8字节数组,导致两端使用的实际密钥不一致,签名校验失败。
最终解决方案
调整密钥处理逻辑,先对配置的密钥字符串做BASE64解码,再对不足长度的密钥做补位处理,最终配置如下:
@Bean public JwtDecoder jwtDecoder(@Value("${jwt.secret-key}") String secretKey) { var key = TextCodec.BASE64.decode(secretKey); var paddedKey = key.length < 128 ? Arrays.copyOf(key, 128) : key; return NimbusJwtDecoder .withSecretKey(new SecretKeySpec(paddedKey, "HS512")) .macAlgorithm(MacAlgorithm.HS512) .build(); }
内容的提问来源于stack exchange,提问作者Andrea Damiani</think_never_used_51bce0c785ca2f68081bfa7d91973934># Spring Security OAuth2资源服务器短HS512密钥签名校验问题解决
问题背景
正在开发基于Spring Boot的资源服务器,需要使用Spring Security OAuth2 Resource Server库的能力。当前遇到的核心限制是:授权服务器为另一款Spring Boot应用,使用早年设置且无法修改的短对称密钥对JWT做HS512签名。
初始配置与密钥长度错误
参照Spring Security官方文档的初始配置如下:
@EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorize -> authorize .anyRequest().permitAll()) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); } @Bean public JwtDecoder jwtDecoder(@Value("${jwt.secret-key}") String secretKey) { return NimbusJwtDecoder .withSecretKey(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HS512")) .macAlgorithm(MacAlgorithm.HS512) .build(); } }
运行后抛出密钥长度不足的异常:
Caused by: com.nimbusds.jose.KeyLengthException: The secret length must be at least 256 bits at com.nimbusds.jose.crypto.impl.MACProvider.<init>(MACProvider.java:118) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1] ... 61 common frames omitted
抛出异常的MACProvider不支持自定义配置,无法直接放宽密钥长度要求。
首次修复尝试与签名校验错误
尝试对密钥做补0处理满足长度要求,配置如下:
@Bean public JwtDecoder jwtDecoder(@Value("${jwt.secret-key}") String secretKey) { var key = secretKey.getBytes(StandardCharsets.UTF_8); var paddedKey = Arrays.copyOf(key, 128); return NimbusJwtDecoder .withSecretKey(new SecretKeySpec(paddedKey, "HS512")) .macAlgorithm(MacAlgorithm.HS512) .build(); }
修改后抛出签名无效异常:
com.nimbusds.jose.proc.BadJWSException: Signed JWT rejected: Invalid signature ... 省略异常栈
根因确认
通过排查授权服务器的JWT生成逻辑,确认其使用io.jsonwebtoken 0.9.1版本库实现签名,生成代码如下:
private String generateToken(Map<String, Object> claims, String username) { Header header = Jwts.header(); header.setType("JWT"); String jti = UUID.randomUUID().toString(); Date now = new Date(System.currentTimeMillis()); return Jwts.builder() .setClaims(claims) .setHeader((Map<String, Object>) header) .setSubject(username) .setIssuedAt(now) .setIssuer("issuer") .setId(jti) .signWith(SignatureAlgorithm.HS512, secret) .compact(); }
旧版本jjwt库的signWith方法接收字符串类型密钥时,会默认先对字符串做BASE64解码,再将解码后的字节数组作为实际签名密钥。而资源服务器初始直接将密钥字符串转为UTF-8字节数组,导致两端使用的实际密钥不一致,签名校验失败。
最终解决方案
调整密钥处理逻辑,先对配置的密钥字符串做BASE64解码,再对不足长度的密钥做补位处理,最终配置如下:
@Bean public JwtDecoder jwtDecoder(@Value("${jwt.secret-key}") String secretKey) { var key = TextCodec.BASE64.decode(secretKey); var paddedKey = key.length < 128 ? Arrays.copyOf(key, 128) : key; return NimbusJwtDecoder .withSecretKey(new SecretKeySpec(paddedKey, "HS512")) .macAlgorithm(MacAlgorithm.HS512) .build(); }
内容的提问来源于stack exchange,提问作者Andrea Damiani

