如何使用 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"));
好的,我在生成 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"));