一,什么是 JW
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准,定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
- 简洁: 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
- 自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库
二,JWT 组成部分
2.1,令牌组成
- 标头:Header
- 有效载荷:Payload
- 签名:Singature
2.2,详细
标头
一个json字符串,包含当前令牌名称,以及加密算法,
{"typ":"JWT","alg":"HS256"}
typ用来标识整个token是一个jwt字符串,alg代表签名和摘要算法,一般签发JWT的时候,只要typ和alg就够了,生成方式是将header部分的json字符串经过Base64Url编码;
载荷
存放有效的信息,可以是标准声明,也可以是自定义的内容,可以在代码中获取;
- 标准声明
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
标准声明,不是强制性的,你可以自定义内容,但是不要存放重要信息,如:用户密码等;
这一部分信息容易被获取。
签名
由头部信息使用base64加密之后,拼接上载荷使用base64加密之后的部分,在加上当前的密钥,进行头部中的加密算法进行加密
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
三,在Java中使用
在Java中使用JWT,有许多的第三方库,如:java-jwt,jjwt等;本文主要介绍这两种库;
3.1,java-jwt
官网
:https://github.com/auth0/java-jwt
1,导入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.0.0</version>
</dependency>
2,代码实现
- 生成JWT
void sign() {
// 创建签名算法
Algorithm algorithm = Algorithm.HMAC256("esyrg");
// 标头
Map<String, Object> headMap = new HashMap<>();
headMap.put("type", "JWT");
headMap.put("alg", "HMAC256");
JWTCreator.Builder builder = JWT.create().withHeader(headMap);
// 载荷
JWTCreator.Builder builder1 = builder.withClaim("iss", "esyrg")
.withClaim("sub", "user");
// 过期时间,2分钟
// 这里设置过期时间不能用Calendar生成Date,否则设置的过期时间无效
long l = System.currentTimeMillis() + (1000 * 60 * 2);
Date date = new Date(l);
String jwt = builder1.withExpiresAt(date).sign(algorithm);
System.out.println(jwt);
}
注意:
设置过期时间不能用Calendar生成Date,否则设置的过期时间无效,至于原因本人也不清楚。
- 验证
void verifyJwt() {
String jwt = "eyJ0eXAiOiJKV1QiLCJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJ1c2VyIiwiaXNzIjoiZXN5cmciLCJleHAiOjE2NjE1MjM5NTh9.FMl14s3GJ5n9sKrpMIYIqgSAQ-isWTTSdT6InUli1lQ";
Algorithm algorithm = Algorithm.HMAC256("esyrg");
DecodedJWT verify = JWT.require(algorithm).build().verify(jwt);
// 获取标头
String header = verify.getHeader();
// 获取载荷或自定义信息
Claim iss = verify.getClaim("iss");
String string = iss.asString();
}
常用方法:
- decodeJwt(String token)
- decode(String token)
以上这两种方法都可以解析获取jwt中的信息,但是这两种方法不会验证jwt的签名。
void verifyJwt() {
String jwt = "eyJ0eXAiOiJKV1QiLCJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJ1c2VyIiwiaXNzIjoiZXN5cmcifQ.zLlDZzys7xEbMD6bwSEdTuscS5LGYEDCAyxlUj5AWx8";
DecodedJWT decode = JWT.decode(jwt);
Claim iss = decode.getClaim("iss");
}
常见异常
令牌过期异常:TokenExpiredException
签名无效或key错误异常:SignatureVerificationException
算法不匹配异常:AlgorithmMismatchException
可用签名算法(摘自官网)
JWS | 算法 | 描述 |
---|---|---|
HS256 | HMAC256 | 带有 SHA-256 的 HMAC |
HS384 | HMAC384 | 带有 SHA-384 的 HMAC |
HS512 | HMAC512 | 带有 SHA-512 的 HMAC |
RS256 | RSA256 | 带有 SHA-256 的 RSASSA-PKCS1-v1_5 |
RS384 | RSA384 | 带有 SHA-384 的 RSASSA-PKCS1-v1_5 |
RS512 | RSA512 | 带有 SHA-512 的 RSASSA-PKCS1-v1_5 |
ES256 | ECDSA256 | 曲线 P-256 和 SHA-256 的 ECDSA |
ES384 | ECDSA384 | 具有曲线 P-384 和 SHA-384 的 ECDSA |
ES512 | ECDSA512 | 曲线 P-521 和 SHA-512 的 ECDSA |
3.2,jjwt
官网
:https://github.com/jwtk/jjwt
导入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
代码实现
- 生成JWT
void contextLoads() {
// 标头
Map<String, Object> headMap = new HashMap<>();
headMap.put("type", "JWT");
headMap.put("alg", "HS256");
// 自定义信息
Map<String, Object> cliMap = new HashMap<>();
cliMap.put("id", "1");
cliMap.put("name", "ren");
// 过期时间
long l = System.currentTimeMillis() + (1000 * 60 * 2);
Date date = new Date(l);
JwtBuilder jwtBuilder = Jwts.builder().setHeader(headMap)
.setClaims(cliMap)
.setExpiration(date);
// 签名,这里有两种方法
// 第一种,此方法要求key长度必须足够长,否则会报错
// SecretKey secretKey = Keys.hmacShaKeyFor("esyrgfdfdfdf".getBytes());
// String jwt = jwtBuilder.signWith(secretKey,SignatureAlgorithm.HS256).compact();
// 第二种,建议采用此法方法
// SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jwt = jwtBuilder.signWith(secretKey).compact();
System.out.println(jwt);
}
- 解析JWT
void verifyJwt() {
String jwt = "eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJuYW1lIjoicmVuIiwiaWQiOiIxIn0.6LxD4SdCPVYbbijePBGQu2oIRTa23ZsI-wHMzhYfIx8";
// 这里的secretKey必须和生成时用的secretKey一样
Jwt parse = Jwts.parserBuilder().setSigningKey(secretKey).build().parse(jwt);
Claims body = (Claims) parse.getBody();
Object name = body.get("name");
Header header = parse.getHeader();
Object type = header.get("type");
// 还可以以下面这种方式解析jwt
// Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(jwt);
// Claims body = claimsJws.getBody();
// JwsHeader header = claimsJws.getHeader();
}
常见异常
令牌过期异常:ExpiredJwtException
签名或jwt错误:SignatureException
四,JWT工具类
以下为JWT工具类,使用的依赖是java-jwt。
/**
* JWT工具类
* <p>
* 使用的是java-jwt
*
* @Author: Esyrg
* @Date: 2022/8/27 17:57
*/
public class JWTUtils {
/**
* 生成签名
*
* @param claims 附带信息
* @param secret 生成token的密钥
* @param expireTime 过期时间,单位:秒
* @return 加密的token
*/
public static String sign(Map<String, String> claims, String secret, Long expireTime) {
Date date = new Date(System.currentTimeMillis() + expireTime * 1000);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带信息
JWTCreator.Builder builder = JWT.create();
for (String key : claims.keySet()) {
builder.withClaim(key, claims.get(key));
}
// 过期时间
builder.withExpiresAt(date);
return builder.sign(algorithm);
}
/**
* 验证token
*
* @param token 要验证的token
* @param secret 密钥
* @return 是否正确
*/
public static boolean verify(String token, String secret) {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier build = JWT.require(algorithm).build();
build.verify(token);
return true;
}
/**
* 验证token
*
* @param token 要验证的token
* @param secret 密钥
* @return 状态及信息,1:正确,0:错误
*/
public static Map<String, Object> verifyInfo(String token, String secret) {
Map<String, Object> map = new HashMap<>();
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier build = JWT.require(algorithm).build();
build.verify(token);
map.put("state", "1");
map.put("message", "令牌正确");
return map;
} catch (TokenExpiredException e) {
// 需要打印时,可以去掉注释
// e.printStackTrace();
map.put("state", "0");
map.put("message", "令牌过期");
return map;
} catch (SignatureVerificationException e) {
// e.printStackTrace();
map.put("state", "0");
map.put("message", "验证错误");
return map;
} catch (AlgorithmMismatchException e) {
// e.printStackTrace();
map.put("state", "0");
map.put("message", "算法不匹配");
return map;
} catch (Exception e) {
// e.printStackTrace();
map.put("state", "0");
map.put("message", e.getMessage());
return map;
}
}
/**
* 获取token中的信息,无需解密也能获得
*
* @param token 要解析的token
* @param key 信息的key
* @return 信息
*/
public static String getClaims(String token, String key) {
try {
DecodedJWT decode = JWT.decode(token);
return decode.getClaim(key).asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 获取过期时间,无需解密也能获得
*
* @param token 需要解析的token
* @return 过期时间
*/
public static Date getExpiresAt(String token) {
try {
DecodedJWT decode = JWT.decode(token);
return decode.getExpiresAt();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 获取token的剩余时间,无需解密也能获得,单位:毫秒
*
* @param token 需要解析的token
* @return 剩余时间,如果已过期,则返回 -1
*/
public static Long getExpiresAtMill(String token) {
try {
DecodedJWT decode = JWT.decode(token);
long l = decode.getExpiresAt().getTime() - System.currentTimeMillis();
return l < 0 ? -1L : l;
} catch (JWTDecodeException e) {
return null;
}
}
}