Spring Boot资源服务器自定义JWT解码器:JWT转Spring Security Jwt对象时的时间戳类型错误求助
解决Spring Security JwtDecoder中时间戳类型不匹配的问题
你遇到的问题根源很明确:Nimbus JWTParser解析出来的exp、iat这类时间戳claim是Date类型,但Spring Security的Jwt.Builder要求这些时间字段必须是Instant类型。你之前的尝试只是单独设置了expiresAt,但没有移除原claims map里的Date类型exp值,所以Builder在处理整个claims集合时还是会触发类型检查异常。
下面是正确的解决步骤和完整代码:
核心修复思路
- 先从Nimbus解析后的claims中提取时间相关字段(
exp、iat、nbf等),转换为Instant类型后通过Jwt.Builder的专属方法设置。 - 从原claims map中移除这些时间字段,避免和Builder设置的
Instant值冲突。 - 将剩余的claims放入Jwt.Builder中,确保所有字段类型符合要求。
完整的自定义JWTDecoder实现
import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTParser; import com.nimbusds.jwt.JWTClaimsSet; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtException; import java.text.ParseException; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; public class CustomJWTDecoder implements JwtDecoder { // 替换为你的实际AES解密密钥 private String aesSecretKey = "your-aes-secret-key-here"; @Override public Jwt decode(String encryptedToken) throws JwtException { try { // 第一步:解密AES加密的token String decryptedToken = decryptAES(encryptedToken); // 第二步:用Nimbus解析解密后的JWT字符串 JWT parsedJwt = JWTParser.parse(decryptedToken); JWTClaimsSet claimsSet = parsedJwt.getJWTClaimsSet(); // 第三步:转换为Spring Security的Jwt对象 return createSpringJwt(decryptedToken, parsedJwt, claimsSet); } catch (ParseException e) { throw new JwtException("解析JWT失败: " + e.getMessage(), e); } catch (Exception e) { throw new JwtException("解密或处理JWT失败: " + e.getMessage(), e); } } private String decryptAES(String encryptedToken) throws Exception { // 这里实现你的AES解密逻辑,示例伪代码: // Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // SecretKeySpec keySpec = new SecretKeySpec(aesSecretKey.getBytes(), "AES"); // cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(yourIvBytes)); // return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedToken))); return encryptedToken; // 临时返回原token,实际开发替换为真实解密逻辑 } private Jwt createSpringJwt(String tokenValue, JWT parsedJwt, JWTClaimsSet claimsSet) { // 提取JWT头部信息 Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject()); // 复制所有claims,后续移除时间相关字段 Map<String, Object> claims = new LinkedHashMap<>(claimsSet.getClaims()); // 初始化Jwt构建器 Jwt.Builder jwtBuilder = Jwt.withTokenValue(tokenValue) .headers(h -> h.putAll(headers)); // 处理过期时间exp Date expDate = claimsSet.getExpirationTime(); if (Objects.nonNull(expDate)) { jwtBuilder.expiresAt(expDate.toInstant()); claims.remove("exp"); // 移除原Date类型的exp字段 } // 处理签发时间iat Date iatDate = claimsSet.getIssueTime(); if (Objects.nonNull(iatDate)) { jwtBuilder.issuedAt(iatDate.toInstant()); claims.remove("iat"); // 移除原Date类型的iat字段 } // 处理生效时间nbf(如果存在) Date nbfDate = claimsSet.getNotBeforeTime(); if (Objects.nonNull(nbfDate)) { jwtBuilder.notBefore(nbfDate.toInstant()); claims.remove("nbf"); // 移除原Date类型的nbf字段 } // 放入剩余的非时间类型claims jwtBuilder.claims(c -> c.putAll(claims)); return jwtBuilder.build(); } }
为什么之前的代码会报错?
你之前的代码先调用了claims(c -> c.putAll(claims)),把包含Date类型exp的map放进去,之后再调用expiresAt设置Instant值。但此时claims map里的exp还是Date类型,Spring Security的Jwt.Builder在验证所有claim类型时,会检查到exp的类型不符合要求(必须是Instant),所以依然抛出异常。
通过先移除时间相关的claim,再用Builder的专属方法设置Instant值,就能避免这个类型冲突问题。
安全配置中的正确使用
建议通过@Bean方式注入自定义解码器,方便后续扩展和依赖注入:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public JwtDecoder customJwtDecoder() { return new CustomJWTDecoder(); } @Override protected void configure(HttpSecurity http) throws Exception { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRoleConverter()); http .authorizeRequests() .antMatchers(HttpMethod.GET, "/users/status/check") .hasRole("developer") .anyRequest().authenticated() .and() .oauth2ResourceServer() .jwt() .decoder(customJwtDecoder()) .jwtAuthenticationConverter(jwtAuthenticationConverter); } }
内容的提问来源于stack exchange,提问作者Shahab.es
相关产品推荐
相关产品推荐

