如何使用 JJWT 从有效负载中获取自定义字段

How do I get a custom field out of the payload using JJWT

好的,我在生成 JWT 时向有效负载添加了几个自定义声明,我可以在我的前端中很好地提取它们 (javascript)。然后,我让我的 javascript 向微服务发送 ajax 调用,并将 JWT 与其一起传递。我想从微服务中的 JWT 中获取我的自定义声明。我正在执行以下操作:

Claims claims = Jwts.parser().setSigningKey(Vars.SECRET_KEY).parseClaimsJws(token).getBody();
 User user = claims.get("customuser", User.class);

并抛出异常。

io.jsonwebtoken.RequiredTypeException: Expected value to be of type: class net.netdatacorp.netdauth.model.User, but was class java.util.LinkedHashMap
    at io.jsonwebtoken.impl.DefaultClaims.get(DefaultClaims.java:128)

这是我的自定义声明在前端的 JWT 检查器中的数据显示方式。

{
  jti: "83bffbad-7d36-4370-9332-21a84f2a3dce",
  iat: 1498241526,
  sub: "test",
  iss: "www.test.net",
  customuser: {
    userId: 1,
    userCd: "TMM",
    firstNm: "Testy",
    lastNm: "McTesty",
    userNm: "test",
    emailAddress: "jacob@test.net",
    active: true,
    createdDt: 1491355712000,
    createdByUserId: 0,
    lastUpdateDt: 1498199278000,
    lastUpdateByUserId: 0,
    lastLoginDt: 1484928016000
  }
}

我缺少什么才能取消自定义索赔?

好的,所以我转而使用 Jose4J 而不是 JJWT,在努力让所有事情都正常工作之后,我意识到我可能可以用 JJWT 做类似的事情。所以我最终做的是使用 Gson 对对象执行 JSON 编码,并将生成的 JSON 字符串作为声明附加。因此,当我想收回自定义声明时,我会将声明提取为字符串,并使用 Gson 库将其转换回 POJO。

GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();

JwtConsumer jwtConsumer = getConsumer();

JwtClaims jwtClaims = jwtConsumer.processToClaims(token);
String userStr = jwtClaims.getClaimValue("user", String.class);
User user = gson.fromJson(userStr, User.class);

我们可以使用 Jackson 的对象映射器将 Claims(这是一个 Map<String, Object>)转换为我们的自定义声明 java 对象。

final ObjectMapper mapper = new ObjectMapper();

Claims jwsMap = Jwts.parser()
       .setSigningKey("SECRET")
       .parseClaimsJws("jwt")
       .getBody();
return mapper.convertValue(jwsMap, MyCustomClaim.class);

同时添加该代码以尝试捕获以确保我们处理 missing/tampered 签名的情况。

JJWT 从 0.11.0 版本开始就有了这个功能。

这个想法是 JWT 库本身不应该进行编组行为,因为 1) 能够处理任何临时数据结构(参见 JAXB 和 Jackson 代码库作为示例)是一项非常复杂的工作,以及 2) JSON marshaller 已经可以做到了 - JJWT 重新发明那个轮子是没有意义的。

因此,为了利用编组器的内置支持,我们需要告诉它应该查看哪些字段以将其解组为自定义对象,以便它可以在解析时执行此操作。 (当 JSON 被完全解析时,JJWT 开始查看 JWT Map 时已经 'too late' 了,所以我们需要确保编组器可以在解析时完成它)。

您可以通过告诉编组器哪些字段应转换为自定义类型来执行此操作,例如,使用 Jackson:

Jwts.parserBuilder()

    .deserializeJsonWith(new JacksonDeserializer(Maps.of("user", User.class).build())) // <-----

    .build()

    .parseClaimsJwt(aJwtString)

    .getBody()
    
    .get("user", User.class) // <-----

有关详细信息,请参阅位于 https://github.com/jwtk/jjwt#parsing-of-custom-claim-types

的 JJWT 文档

我知道你的主要目标是Customer对象。索赔对象中已存在其他数据。您可以像这样轻松管理自己的对象。

@Data //insted of this annotation, you can generate the getters and setters
@JsonIgnoreProperties(ignoreUnknown = true)
public class Customer {
    private Integer userId;
    private String userCd;
    private String firstNm;
    ........
}

JsonIgnoreProperties注解在从token body转为object的时候非常重要。它忽略了对象没有的其他属性。 (Jti,Lat,Iss)

现在您拥有了想要的对象。让我们生成令牌。

Map<String, Object> claims = new HashMap<>(); //create a hashmap
Customer customer= new Customer(); //create your object

//assign the initial customer data
customer.setUserId(1);
customer.setUserCd("TMM");
customer.setFirstNm("Testy");

ObjectMapper oMapper = new ObjectMapper(); //create a objectmapper object
Map<String, Object> customerData = oMapper.convertValue(customer, Map.class); //convert the customer object into map of (String, Object)
claims.putAll(customerData ); //put all the customer data into claims map


//create the token using another required data
String token = Jwts.builder()
                .setClaims(claims) //this our object
                .setSubject("test")
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
                .signWith(SignatureAlgorithm.HS512, "secret")
                .compact();


https://jwt.io/把生成的token放进去看看怎么样。会是这样的。

{
  "sub": "test",
  "firstNm": "Testy", //customer data from the object
  "exp": 1622862855,
  "userId": 1, //customer data from the object
  "iat": 1622844855,
  "userCd": "TMM" //customer data from the object,
  ........
}

它也包含您的自定义客户数据的所有数据。

现在让我们解码令牌

Jws<Claims> claimsJws = Jwts.parser().setSigningKey("secret").parseClaimsJws(token);
ObjectMapper mapper = new ObjectMapper();
Customer customer = mapper.convertValue(claimsJws.getBody(), Customer.class); //convert the claims body by mentioning the customer object class
System.out.println("customerData = " + customer);

现在您可以根据需要使用客户数据对象。 **特别之处在于 @JsonIgnoreProperties(ignoreUnknown = true) 注释。

向 JWT 添加自定义声明。

注意:我在Spring安全

中使用了这个

保留声明

  • iss – 发行人
  • sub – 主题
  • aud – 观众
  • exp – 过期
  • nbf – 之前没有
  • iat – 发布时间
  • jti – JWT ID

添加自定义声明

String token = Jwts.builder()
.setSubject(subject)
.setExpiration(expDate)
.claim("userId", "3232")
.claim("UserRole", "Admin")
.signWith(SignatureAlgorithm.HS512, secret )
.compact();

检索自定义声明

Claims claims = Jwts.parser()         
   .setSigningKey(tokenSecret)
   .parseClaimsJws(jwt).getBody();
 
// Reading Reserved Claims
System.out.println("Subject: " + claims.getSubject());
System.out.println("Expiration: " + claims.getExpiration());

// Reading Custom Claims
System.out.println("userId: " + claims.get("userId"));
System.out.println("userRole: " + claims.get("userRole"));