JWT & JJWT
JWT
本文采用 HS256 编码方式
Tips:你需要知道 JWT 由【标头(Header)、有效载荷(Payload)、签名(Verify Signature)】这三部分组成
- https://github.com/auth0/java-jwt
1. 导包
<!-- jwt 依赖包 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.3</version>
</dependency>
2. 生成一个 key 密钥
Algorithm algorithmHS = Algorithm.HMAC256("my secret");
3. 创建一个 token
try {
Algorithm algorithm = Algorithm.HMAC256("my secret");
String token = JWT.create()
.withIssuer("mlrl")
.sign(algorithm);
log.info((token);
} catch ( exception){
//Invalid Signing configuration / Couldn't convert Claims.
}
输出:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtbHJsIn0.o4t9ma-6QT6urz63qTlATCQiX8cb3uywO_QPNTxfcwQ
4. 一些token的验证
iss:jwt前发签发者
sub:jwt所面向的用户
aud:接收jwt的乙方
exp:jwt的过期时间,必须要大于签发时间
nbf:定义在什么时间之前,该jwt都是不可用的
iat:jwt的签发时间
jti:jwt的唯一身份表示,主要用来作为一次性token,从而回避重放攻击
- 验证 token 签名是否匹配
try {
Algorithm algorithm = Algorithm.HMAC256("my secret");
String token = JWT.create()
.withIssuer("mlrl")
.sign(algorithm);
// 创建验证 token 的验证工具
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("123")
.build(); //Reusable verifier instance
// 执行验证
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception) {
log.error("验证token 失败" + exception.getMessage());
}
- 验证 token 签名是否匹配
try {
Algorithm algorithm = Algorithm.HMAC256("my secret");
String token = JWT.create()
.withIssuer("mlrl")
.sign(algorithm);
// 输出 token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtbHJsIn0.Mc7rezSs-Ym-XdFulbqFXqCsis5phSIHbY3VZuCdicQ";
// 创建验证 token 的验证器类
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("123")
.build(); //Reusable verifier instance
// 执行验证
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception) {
log.error("验证token 失败" + exception.getMessage());
}
输出:验证token 失败The Claim 'iss' value doesn't match the required issuer.
原因:创建token的 withIssuer("mlrl") 与 解析的 withIssuer("123") 不一致导致验证失败
- 时间验证(有点迷,博主有点不太明白怎么设置时间)
Time Validation
The JWT token may include DateNumber fields that can be used to validate that:
- The token was issued in a past date “iat” < TODAY
- The token hasn’t expired yet “exp” > TODAY and
- The token can already be used. “nbf” < TODAY
When verifying a token the time validation occurs automatically, resulting in a JWTVerificationException being throw when the values are invalid. If any of the previous fields are missing they won’t be considered in this validation.
To specify a leeway window in which the Token should still be considered valid, use the acceptLeeway() method in the JWTVerifier builder and pass a positive seconds value. This applies to every item listed above.
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) // 1 sec for nbf, iat and exp
.build();
You can also specify a custom value for a given Date claim and override the default one for only that claim.
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) //1 sec for nbf and iat
.acceptExpiresAt(5) //5 secs for exp
.build();
If you need to test this behaviour in your lib/app cast the Verification instance to a BaseVerification to gain visibility of the verification.build() method that accepts a custom Clock. e.g.:
BaseVerification verification = (BaseVerification) JWT.require(algorithm)
.acceptLeeway(1)
.acceptExpiresAt(5);
Clock clock = new CustomClock(); //Must implement Clock interface
JWTVerifier verifier = verification.build(clock);
Decode a Token
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
//Invalid token
}
If the token has an invalid syntax or the header or payload are not JSONs, a JWTDecodeException will raise.
- 验证 Claim(s)
例1 JWT - Header Claims
— header 部分,用不到验证(创建验证器的时候,根本没有与 header 相关的方法)。
Map<String, Object> headerClaims = new HashMap();
headerClaims.put(“who are you”, “Peppa Pig”);
String token = JWT.create().withHeader(headerClaims).sign(algorithm);
try {
Algorithm algorithm = Algorithm.HMAC256("my secret");
Map<String, Object> headerClaims = new HashMap();
headerClaims.put("who are you", "Peppa Pig");
headerClaims.put("Apple", "Apple Pen");
String token = JWT.create()
.withHeader(headerClaims)
.sign(algorithm);
log.info("token " + token);
JWTVerifier verifier4 = JWT.require(algorithm)
.build();
DecodedJWT jwt = verifier4.verify(token);
Claim claim1 = jwt.getHeaderClaim("who are you");
log.info(String.valueOf(claim1)); // 输出 "Peppa Pig"
Claim claim2 = jwt.getHeaderClaim("Apple");
log.info(String.valueOf(claim2)); // 输出 "Apple Pen"
} catch (Exception e) {
log.error("token 验证失败" + e.getMessage() + e.getCause());
}
// header 的其他参数
Algorithm ("alg")
Returns the Algorithm value or null if it's not defined in the Header.
String algorithm = jwt.getAlgorithm();
Type ("typ")
Returns the Type value or null if it's not defined in the Header.
String type = jwt.getType();
Content Type ("cty")
Returns the Content Type value or null if it's not defined in the Header.
String contentType = jwt.getContentType();
Key Id ("kid")
Returns the Key Id value or null if it's not defined in the Header.
String keyId = jwt.getKeyId();
例2 JWT - Payload Claims 2种方式
— 这里就需要验证了
Map<String, Claim> claims = jwt.getClaims();
Claim claim = claims.get(“who are you”); // Key is the Claim name
或者
Claim claim = jwt.getClaim(“isAdmin”);
// 方式1
try {
Algorithm algorithm = Algorithm.HMAC256("my secret");
String token = JWT.create()
.withClaim("name", 123)
.withArrayClaim("array", new Integer[]{1, 2, 3})
.sign(algorithm);
log.info("token " + token);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("name", 13)
// .withArrayClaim("array", new Integer[]{1, 2})
.build();
DecodedJWT jwt = verifier.verify(token);
} catch (Exception e) {
log.error("token 验证失败" + e.getMessage() + e.getCause());
}
输出:token 验证失败The Claim 'name' value doesn't match the required one.null
原因:创建 token 的时候,添加了2个载荷,那么,创建验证器的时候,也应该添加对应的载荷(2选其一能对应即可),否则报错
// 方式2
try {
Algorithm algorithm = Algorithm.HMAC256("my secret");
Map<String, Object> payloadClaims = new HashMap<>();
payloadClaims.put("@context", "https://www.baidu.com/");
String token = JWT.create()
.withPayload(payloadClaims)
.sign(algorithm);
log.info("token " + token);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("@context", "https://www.4399.com/")
.build();
DecodedJWT jwt = verifier.verify(token);
} catch (Exception e) {
log.error("token 验证失败" + e.getMessage() + e.getCause());
}
输出:token 验证失败The Claim '@context' value doesn't match the required one.null
// Paylod claims 其他参数
Issuer ("iss")
Returns the Issuer value or null if it's not defined in the Payload.
String issuer = jwt.getIssuer();
Subject ("sub")
Returns the Subject value or null if it's not defined in the Payload.
String subject = jwt.getSubject();
Audience ("aud")
Returns the Audience value or null if it's not defined in the Payload.
List<String> audience = jwt.getAudience();
Expiration Time ("exp")
Returns the Expiration Time value or null if it's not defined in the Payload.
Date expiresAt = jwt.getExpiresAt();
Not Before ("nbf")
Returns the Not Before value or null if it's not defined in the Payload.
Date notBefore = jwt.getNotBefore();
Issued At ("iat")
Returns the Issued At value or null if it's not defined in the Payload.
Date issuedAt = jwt.getIssuedAt();
JWT ID ("jti")
Returns the JWT ID value or null if it's not defined in the Payload.
String id = jwt.getId();
JJWT
- https://github.com/jwtk/jjwt
Java JWT: JSON Web Token for Java and Android
JJWT aims to be the easiest to use and understand library for creating and verifying JSON Web Tokens (JWTs) on the JVM and Android.
JJWT is a pure Java implementation based exclusively on the JWT, JWS, JWE, JWK and JWA RFC specifications and open source under the terms of the Apache 2.0 License.
The library was created by Les Hazlewood and is supported and maintained by a community of contributors.
We’ve also added some convenience extensions that are not part of the specification, such as JWS compression and claim enforcement.
其实和 auth0 的 JWT 类似,只不过 jjwt 是基于 jwt 实现的,一样可用
1. 导包
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- 生成 jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<!-- 以下两个包用于解析 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.2</version>
<scope>compile</scope>
</dependency>
2. 创建密钥
public void generateSecretKey() {
private static final Logger log = LoggerFactory.getLogger(jwtTest.class);
// 获得随机 key,并加密 => 密钥
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 对密钥编码加密 => 加密密钥
String secretString = Encoders.BASE64.encode(key.getEncoded());
// 【这个就是我们需要的密钥,执行一次即可获得,一般保存在服务器上】
log.info(secretString);
/**
* 【这里对 secretString 进行解析;生成 jwt 需要用到上面生成的 key 】
* 【用 secretString 进行解析得到 key;保证 key 为同一个,否则解析 jwt 的时候会报错】
*/
// 对加密密钥编码解密 => 密钥
byte[] decode = Decoders.BASE64.decode(secretString);
// 对密钥解密 => 获得 key
Key key2 = Keys.hmacShaKeyFor(decode);
log.info(String.valueOf(key));
log.info(String.valueOf(key2));
log.info(String.valueOf(key.equals(key2)));
}
【这里解释有误欢迎留言或私信指正!】
由此可见,key 的值 == key2 的值,说明获取到加密或解密的是同一个密钥,并指向同一个空间
13:47:27.492 [main] INFO com.mblooms.web.util.JWTUtil - javax.crypto.spec.SecretKeySpec@5882c82
13:47:27.494 [main] INFO com.mblooms.web.util.JWTUtil - javax.crypto.spec.SecretKeySpec@5882c82
13:47:27.494 [main] INFO com.mblooms.web.util.JWTUtil - true
3. 创建 jwt
public JwtBuilder generateJWT() {
JwtBuilder builder = Jwts.builder()
.setId(UUID.randomUUID()) // JWT_ID
.setIssuer(" me ") // 签发者
.setSubject(" metting ") // 主题
.setAudience(" you ") // 接受者
.setClaims(null) // 自定义属性
.setExpiration(long) // 到期时间
.setIssuedAt(new Date()) // 签发时间---
.setNotBefore(new Date()) // 失效时间---
.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙 - 上面生成的 secretString
return builder.compact();
}
signWith进行签名,建议采用以下两种方式
JwtBuilder signWith(Key var1) throws InvalidKeyException; // key
JwtBuilder signWith(Key var1, SignatureAlgorithm var2) throws InvalidKeyException; // key,算法
4. 测试
public class jwtTest {
private static final Logger log = LoggerFactory.getLogger(jwtTest.class);
private final String secretKey = "+e41yyBc4FqaTlOks1imtogoBsN/0WxMR5RLjl/phME=";
@Test
public void test(){
log.info("创建JWT中···");
byte[] decode = Decoders.BASE64.decode(secretKey);
Key key = Keys.hmacShaKeyFor(decode);
// 到期时间,一周
Date expirationDate = Date.from(LocalDateTime.now().plusDays(7).atZone(ZoneId.systemDefault()).toInstant());
Map<String,String> claims = getMyClaimsMap();
String jwt = Jwts.builder()
.setId(UUID.randomUUID().toString())
.setIssuer("mblooms")
.setSubject("mbloomsToken")
.setIssuedAt(new Date())
.setExpiration(expirationDate)
.setClaims(claims)
.signWith(key)
.compact();
log.info("======"+jwt+"======");
log.info("创建完成!");
log.info(jwt);
}
/**
* <p>
* 设置加密的参数
* </p>
* @return map
*/
public Map<String,String> getMyClaimsMap(){
Map<String,String> map = new HashMap();
map.put("myClaims1","1");
map.put("myClaims2","2");
map.put("myClaims3","3");
return map;
}
}