JWT-(一)JWT认识和应用

本文最后更新于:May 13, 2023 pm

JSON Web 令牌是一种开放的行业标准 RFC 7519方法,用于在两方之间安全地表示声明。JSON Web Token (JWT) 是一个开放标准 ( RFC 7519 ),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全传输信息。此信息可以验证和信任,因为它是数字签名的。JWT 可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。官网

目录

JWT结构

令牌组成

    1. 标头(Header)
    1. 有效载荷(Payload)
    1. 签名(Signature)

JWT通常形式为:xxxx.yyyy.zzzz,对应Header.Payload.Signature。

注意:Base64是一种编码,而不是加密。Base64编码是可以被还原的。(可逆的)

标头由两部分组成:令牌的类型(即JWT)和所使用的签名算法。例如:HMACSHA256(HS256) 或RSA。还会进行Base64编码,进而组成JWT的第一部分。

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

上述是简写了的:alg(Algorithm)、typ(type)。

Payload

有效负载,其中包含声明。声明有关实体(通常为用户)和其他数据的声明(简单说就是用户信息,但不要放用户的敏感信息!)。也是进行Base64编码,进而组成JWT的第二部分。

1
2
3
4
5
{
"id": "123456",
"name": "loong",
"role": "admin"
}

Signature

前两者(Header、Payload)用Base64编码后,再进行Header中指定的算法(本例中是 HS256)进行签名,最后组成Signature(不要忘记点号(.))。最后组成JWT的第三部分。

1
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret); //其中secret就是Header中的算法HS256,base64UrlEncode是进行base64编码

参考

如果一个令牌为:xxx.yyy.zzz,那么,

  • xxx = base64UrlEncode(header)

  • yyy = base64UrlEncode(payload)

所以,如果 T = xxx+yyy ,那么:

  • T = base64UrlEncode(header).base64UrlEncode(payload)

如果,zzz = T + 签名,那么:

  • zzz = T + 签名;

  • zzz = HMACSHA256(T,secret);

  • zzz = HMACSHA256(base64UrlEncode(header)+”.”+base64UrlEncode(payload),secret)

所以,最后的结果为:

1
2
3
base64UrlEncode(header).
base64UrlEncode(payload).
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

生成Token

导入依赖

这里以java-jwt为例:

1
2
3
4
5
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.3</version>
</dependency>
1
2
3
4
5
6
7
8
payload官方定义包含属性如下(非强制):
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
void contextLoads() {
Algorithm alg = Algorithm.HMAC256("sdfwefe"); //自定义签名密钥
Map<String,Object> header = new HashMap<>(); //设置header
Calendar ins = Calendar.getInstance();
ins.add(Calendar.SECOND,60); //设置过期时间为60秒

String token = JWT.create()
//header
.withHeader(header) //如果不设置,即删除不要,则默认为 "alg": "HS256", "typ": "JWT"
.withIssuedAt(new Date()) //签发时间
//Payload
.withClaim("userid",23)
.withClaim("username","loong")
.withExpiresAt(ins.getTime()) //设置过期时间
//签名
.sign(alg);

System.out.println(token);
}

放置的位置有一定的限制!签名放最后,不能在签名后再加其他的。

验证Token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Test
void contextLoads2() {
Algorithm alg = Algorithm.HMAC256("sdfwefe"); //自定义签名密钥
JWTVerifier jwtVerifier = JWT.require(alg).build();
//待验证token
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDM5NDk0MzAsImlhdCI6MTY0Mzk0OTM3MCwidXNlcmlkIjoyMywidXNlcm5hbWUiOiJsb29uZyJ9.s2rvfcW5wSvEtCDP0189Sxh7LItbWZKMG2xZ4-Rb220";
DecodedJWT jwt = jwtVerifier.verify(token); //验证指定token

//获取token中的header
String decodeHeader = StringUtils.newStringUtf8(Base64.decodeBase64(jwt.getHeader()));
//获取token中的payload
String decodePayload = StringUtils.newStringUtf8(Base64.decodeBase64(jwt.getPayload()));
//获取token中的signature
String signature = jwt.getSignature();

//获取自定义数据
int userid = jwt.getClaim("userid").asInt();
String username = jwt.getClaim("username").asString();

System.out.println("header:" + jwt.getHeader());
System.out.println("payload:" + jwt.getPayload());
System.out.println("signature:" + signature);

System.out.println("headerString:" + decodeHeader);
System.out.println("payloadString:" + decodePayload);

System.out.println("nameid:" + userid);
System.out.println("username:" + username);

System.out.println("过期时间为: "+ jwt.getExpiresAt());
}

输出:

1
2
3
4
5
6
7
8
header:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload:eyJleHAiOjE2NDM5NDk0MzAsImlhdCI6MTY0Mzk0OTM3MCwidXNlcmlkIjoyMywidXNlcm5hbWUiOiJsb29uZyJ9
signature:s2rvfcW5wSvEtCDP0189Sxh7LItbWZKMG2xZ4-Rb220
headerString:{"typ":"JWT","alg":"HS256"}
payloadString:{"exp":1643949430,"iat":1643949370,"userid":23,"username":"loong"}
nameid:23
username:loong
过期时间为: Fri Feb 04 12:37:10 CST 2022

封装工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.tothefor.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

public class JWTUtils {
//自定义签名密钥
private static final String SIG = "123432@#@$#^$^%#^%#89";

/**
* 生成token
* @param map 自定义数据
* @return String
*/

public static String getToken(Map<String,String> map){
Algorithm alg = Algorithm.HMAC256(SIG); //自定义签名密钥
Calendar ins = Calendar.getInstance();
ins.add(Calendar.DATE,7); //默认过期时间为7天
//jwt builder
JWTCreator.Builder builder = JWT.create();
//payload
map.forEach((k,v)->{
builder.withClaim(k,v);
});
//signature
String token = builder.withExpiresAt(ins.getTime()).sign(alg);

return token;
}

/**
* 获取token信息
* @param token
* @return DecodedJWT
*/
public static DecodedJWT getTokenInfo(String token){
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIG)).build().verify(token);
return verify;
}
}