You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 08:09:52