如何使用 JJWT 从有效负载中获取自定义字段
Posted
技术标签:
【中文标题】如何使用 JJWT 从有效负载中获取自定义字段【英文标题】:How do I get a custom field out of the payload using JJWT 【发布时间】:2017-11-27 10:18:32 【问题描述】:好的,我在生成 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
为了能够提取我的自定义声明,我缺少什么?
【问题讨论】:
我看了一下,看到提供的JwtParser
的唯一实现是DefaultJwtParser
,它依赖于杰克逊的ObjectMapper
,但遗憾的是没有公开它以允许自定义。甚至不能通过继承:方法 readValue
是 protected
但只会为对象返回 Map
(see here in 0.7.0)。看来您需要自己从头开始实现JwtParser
...或手动将地图转换为您的对象:-/
在那种情况下,我好像要切换库了,看起来 jose4j 可以处理我想做的事情。
@JacobMiles,您介意发布jose4j
代码进行比较吗?
【参考方案1】:
好的,所以我转而使用 Jose4J 而不是 JJWT,在努力让每件事都正常工作之后,我意识到我可能可以用 JJWT 做类似的事情。所以我最终做的是使用 Gson 对 Object 执行 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);
【讨论】:
什么是getConsumer()方法?【参考方案2】:我们可以使用 Jackson 的对象映射器将 Claims(Map<String, Object>
)转换为我们自定义的 Claim java 对象。
final ObjectMapper mapper = new ObjectMapper();
Claims jwsMap = Jwts.parser()
.setSigningKey("SECRET")
.parseClaimsJws("jwt")
.getBody();
return mapper.convertValue(jwsMap, MyCustomClaim.class);
还添加该代码以尝试捕获以确保我们处理丢失/篡改签名的情况。
【讨论】:
请补充一些解释。【参考方案3】:JJWT 自 0.11.0 版本以来就具有此功能。
这个想法是 JWT 库本身不应承担编组行为,因为 1) 能够处理任何临时数据结构(参见 JAXB 和 Jackson 代码库作为示例)和 2) JSON 编组器确实是一项复杂的工作已经可以做到了——JJWT 重新发明那个***没有意义。
因此,为了利用编组器的内置支持,我们需要告诉它应该将哪些字段解组为自定义对象,以便它可以在解析时执行此操作。 (当 JSON 被完全解析时,JJWT 开始查看 JWT Map 时已经“为时已晚”,因此我们需要确保 marshaller 可以在解析时完成。
您可以通过告诉编组器应将哪些字段转换为自定义类型(例如,使用 Jackson)来做到这一点:
Jwts.parserBuilder()
.deserializeJsonWith(new JacksonDeserializer(Maps.of("user", User.class).build())) // <-----
.build()
.parseClaimsJwt(aJwtString)
.getBody()
.get("user", User.class) // <-----
有关更多信息,请参阅 JJWT 的文档https://github.com/jwtk/jjwt#parsing-of-custom-claim-types
【讨论】:
【参考方案4】:我知道您的主要目标是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体转换为对象时非常重要。它忽略了对象没有的其他属性。 (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)
注解。
【讨论】:
【参考方案5】:向 JWT 添加自定义声明。
注意:我在 Spring Security 中使用过这个
保留声明
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"));
【讨论】:
以上是关于如何使用 JJWT 从有效负载中获取自定义字段的主要内容,如果未能解决你的问题,请参考以下文章
带有自定义 iOS 有效负载的 Amazon Simple Notification Service 并不那么简单