如何在JJWT(Jackson)中为JWT Token添加Null Claims
问题:JJWT生成JWT时保留null值的外层Claims
我需要在JWT中设置多个包含null值的Claims,这些属性必须存在,否则客户端会拒绝用户——哪怕值是null。但默认情况下JJWT不会把null值的外层Claims纳入JWT,不过嵌套序列化对象里的null属性却能正常显示。
以下是我添加Claims的代码,以及尝试过的解决方案(配置自定义ObjectMapper并在JWT构建器中使用):
public String generateTokenFromJwtUserDetails(JwtUserDetails userDetails) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + jwtExpiration); Map<String, Object> claims = new HashMap<>(); claims.put("name", userDetails.getName()); // null,不会出现在JWT中 claims.put("id", userDetails.getId()); // 非null,会正常显示 claims.put("tenant", userDetails.getTenant()); // 包含null值,这些null会出现在JWT里 // 其他Claims... // 尝试配置自定义ObjectMapper,但无效 ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); // 也试过这个,同样无效 //objectMapper.enable(SerializationFeature.WRITE_NULL_MAP_VALUES); try{ return Jwts.builder() .json(new JacksonSerializer<>(objectMapper)) .claims(claims) .subject(userDetails.getUsername()) .issuedAt(now) .expiration(expiryDate) .signWith(getSignInKey()) .issuer(jwtIssuer) .compact(); }catch(Exception e){ // 异常处理... } }
补充说明
- 我在JWT里还包含了实体,Jackson序列化这些实体时会保留所有字段(也就是嵌套对象的null值能正常显示,但外层Claims的null值不会被包含),示例JWT内容如下:
{ "id": "ibn12u3bio412i", "tenant": { "deletedOn": null } }
- HashMap里确实包含所有键值对,哪怕值是null。
- 我知道JWT不应该存储null数据,但这个场景下我无法控制客户端的逻辑。
解决方案
问题核心在于:JJWT对顶层Claims和嵌套对象的序列化逻辑是分离的——默认会主动过滤顶层Claims中的null值,而你配置的JacksonSerializer仅负责嵌套对象的序列化,无法影响顶层Claims的过滤规则。
方式一:逐个添加Claims(最简单)
放弃使用claims(Map)批量添加的方式,改用claim(String, Object)方法逐个添加每个键值对。JJWT的claim()方法不会过滤null值,能确保所有属性都出现在JWT中:
return Jwts.builder() .json(new JacksonSerializer<>(objectMapper)) // 保留此配置处理嵌套对象的null序列化 .claim("name", userDetails.getName()) // null会被保留 .claim("id", userDetails.getId()) .claim("tenant", userDetails.getTenant()) // 添加其他Claims... .subject(userDetails.getUsername()) .issuedAt(now) .expiration(expiryDate) .signWith(getSignInKey()) .issuer(jwtIssuer) .compact();
方式二:自定义Claims序列化器(适合批量处理)
如果需要批量处理Claims,可以自定义一个ClaimsSerializer,让它直接用Jackson序列化整个Claims Map,跳过JJWT默认的null过滤逻辑:
public class NullIncludingClaimsSerializer implements ClaimsSerializer { private final ObjectMapper objectMapper; public NullIncludingClaimsSerializer(ObjectMapper objectMapper) { this.objectMapper = objectMapper; // 确保Jackson序列化时包含所有null值 this.objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); } @Override public String serialize(Map<String, ?> claims) throws JsonProcessingException { return objectMapper.writeValueAsString(claims); } }
然后在JWT构建器中指定使用这个自定义序列化器:
ObjectMapper objectMapper = new ObjectMapper(); NullIncludingClaimsSerializer claimsSerializer = new NullIncludingClaimsSerializer(objectMapper); return Jwts.builder() .claimsSerializer(claimsSerializer) .claims(claims) // 批量添加的null值Claims现在会被保留 .subject(userDetails.getUsername()) .issuedAt(now) .expiration(expiryDate) .signWith(getSignInKey()) .issuer(jwtIssuer) .compact();
验证效果
使用上述任意一种方式后,生成的JWT会包含所有顶层null值的Claims,示例如下:
{ "name": null, "id": "ibn12u3bio412i", "tenant": { "deletedOn": null }, "sub": "username", "iat": 1620000000, "exp": 1620003600, "iss": "issuer" }
内容的提问来源于stack exchange,提问作者Mat M
相关产品推荐
相关产品推荐

