JWT
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT结构
token String ===> header.payload.singnature
令牌组成
- 标头(Header)
- 有效载荷(Payload)
- 签名(Signature)
JWT通常如下所示: xxxxx.yyyyy.zzzzz
即 header.payload.singnature
Header
标头通常由两部分组成: 令牌的类型 (即JWT) 和所使用的签名算法, 例如 HMAC SHA256或RSA. 它会使用 Base64 编码组成 JWT 结构的第一部分。
{
"alg": "HS256",
"typ": "JWT"
}
Payload
令牌的第二部分是有效负载, 其中包含声明. 声明是有关实体 (通常是用户) 和其他数据的声明. 同样的, 它会使用 Base64 编码组成 JWT 结构的第二部分。
{
"sub": "1234567890",
"name": "Jack Ma",
"admin": true
}
Signature
前面两部分都是使用 Base64 进行编码的,即前端可以解码并获取里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。
HMACSHA256(
BASE64UrlEncode(Header) + "." +
base64UrlEncode(payload),
secret);
签名的目的
最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改。如果有人对头部以及负载的内容进行解码之后并修改,再进行编码,最后加上之前的签名组合形成新的 JWT 的话,那么服务器会判断出新的头部和负载形成的签名和 JWT 附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知到服务器加密使用的密钥的话,得出来的签名也是不一样的。
使用JWT
导入依赖坐标
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
基本使用
获取token
@Test
void jwtCreate() {
//设置头部,可不设置即默认
Map<String, Object> header = new HashMap<>();
//设置时间
Calendar instance = Calendar.getInstance();
instance.add(Calendar.MINUTE, 30);
//使用create方法获取令牌
String token = JWT.create()
//header
.withHeader(header)
// payload
.withClaim("userId", 00001)
.withClaim("username", "root")
//指定令牌过期时间
.withExpiresAt(instance.getTime())
//signature 指定算法并添加密钥(盐)
.sign(Algorithm.HMAC256("HelloWorld!"));
System.out.println(token);
}
打印结果:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDczMzkxMjEsInVzZXJJZCI6MSwidXNlcm5hbWUiOiJyb290In0.0Qs6CXYvIdKz32OD26VwLnhZLZ1TYwS_W1h49kwDsOQ
验证token
@Test
void verifyToken() {
//获取验签对象 传入算法并添加之前的密钥
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("HelloWorld!")).build();
//验证 通过会返回解码信息
//算法错误抛出 AlgorithmMismatchException
//签名不一致抛出 SignatureVerificationException
//令牌过期抛出 TokenExpiredException
//失效的payload抛出 InvalidClaimException
DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDczMzc4NzcsInVzZXJJZCI6MSwidXNlcm5hbWUiOiJyb290In0.6NRNxZujsOP3x7cvhZluj7VHqD0Do8trVrXzGZn8hx8");
System.out.println("userId---" + verify.getClaim("userId").asInt());
System.out.println("username---"+verify.getClaim("username").asString());
System.out.println("过期时间---"+verify.getExpiresAt());
}
打印结果:
userId---1 username---root 过期时间---Mon Dec 07 18:44:37 CST 2020
验证结果
验证通过会返回解码信息
算法错误抛出 :AlgorithmMismatchException
签名不一致抛出:SignatureVerificationException
令牌过期抛出: TokenExpiredException
失效的payload抛出: InvalidClaimException
封装工具类
public class JwtUtils {
/**
* 设置密钥
*/
private static final String SING = "Hello,World!";
/**
* 令牌过期时间 day
*/
private static final int TIME = 7;
/**
* 生产token
* @param payload 负载信息
* @return token信息
*/
public static String getToken(Map<String, String> payload) {
//过期时间
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, TIME);
//创建token
JWTCreator.Builder builder = JWT.create();
//将Map中的值放入负载中
payload.forEach(builder::withClaim);
//指定过期时间 和 算法
return builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(SING));
}
/**
* 验证token并返回信息
*/
public static DecodedJWT getTokenInfo(String token) {
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
}