【JWT】入门 *JWT*,并封装一个实用的 *JWT* 工具类
1. 什么是JSON Web Token(JWT)?
JSON Web 令牌(JWT)
:
JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于将信息作为 JSON 对象在各方之间安全地传输 。此信息可以被验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
2. JWT使用场景
JWT使用场景
:
- 授权:这是使用 JWT 的最常见方案。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够轻松地跨不同域使用。
- 信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。由于 JWT 可以签名(例如,使用公钥/私钥对),因此您可以确定发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改。
3. JWT结构
JSON Web Token 结构
:
JWT由三部分组成,分别是页眉(Header)、有效载荷(Payload)、签名(Signature) ,他们之间由符号.
进行分割。
- Header
- Payload
- Signature
因此,我们可知,JWT通常是这样的:ddddd.hhhhh.jjjjj
⚪页眉 Header
- Header通常由两部分组成:令牌类型typ、使用的签名算法alg;
{
"alg": "HS256",
"typ": "JWT"
}
然后,这条JSON数据会经过Base64Url编码,组成JWT的第一部分(Header)
⚪有效载荷 Payload
JWT的第二部分是Payload,其中包含的是 Claims(声明), Claims是关于用户实体和其他数据的陈述。
有三种类型的Claims:registered claims
、 public claims
、 private claims
。
-
**已注册的声明(registered claims):**是一组预定义的声明,这些声明不是强制性的,但建议提供一组有用的、可互操作的声明。比如:iss(发行人)、exp(到期时间)、sub(主题)、aud(受众)等…
-
公共声明(public claims):这些声明可以由使用 JWT 的人随意定义。但为了避免冲突,应在 IANA JSON Web Token注册表中定义它们,或者将其定义为包含抗冲突命名空间的 URI。
-
私人声明(private claims):这些声明是为了让同意使用它们的各方之间共享信息而创建的自定义声明,既不是 已注册 声明,也不是 公共 声明。
例如
:
{
"sub": "123456789",
"name": ".29.",
"admin": true
}
接下来,这条JSON数据会经过Base64Url编码,组成JWT的第二部分(Payload)
⚪签名 Signature
要创建签名部分,必须获取经过Base64Url编码后的标头、经过Base64Url编码后的有效负载、密钥、标头中指定的算法,并对其进行签名。
例如,如果要使用 HMAC SHA256
算法,将按以下方式创建签名:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
一个完整的JWT长什么样?
- 下述
JWT
对Header 和 Payload 进行了Base64Url编码,并使用密钥进行了签名,三个三个 Base64-URL 字符串,由点.
进行分隔。 - 它可以在 HTML 和 HTTP 环境中轻松传递,同时与基于 XML 的标准(如 SAML)相比更紧凑。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkiLCJuYW1lIjoiLjI5LiIsImFkbWluIjp0cnVlfQ.1M3o41CutZL1fTjEftuxs6g5ug5M-j6GcP_K61nAIjM
4. JWT基本使用(SpringBoot项目中)
导入maven坐标
:
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- jaxb-api -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<!-- jaxb-impl -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<!-- jaxb-api -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
测试基本使用
:
/**
* @author .29.
* @create 2024-03-15 23:37
*/
@SpringBootTest
public class jwtTest {
//签名密钥
String signature = "admin";
/**
* 加密生成JWT的测试方法
*/
@Test
public void jwt(){
JwtBuilder builder = Jwts.builder(); //获取JWT生成器
//使用JWT生成器创建一个JWT
String jwtToken = builder
//Header
.setHeaderParam("typ", "JWT")//类型
.setHeaderParam("alg", "HS256")//使用的算法
//Payload
.claim("name", ".29.")
.claim("role", "admin")
.setSubject("admin-test")
.setExpiration(new Date(System.currentTimeMillis() + 1000*60*60*24))//失效日期:当前时间+24小时
.setId(UUID.randomUUID().toString())
//signature
.signWith(SignatureAlgorithm.HS256, signature)//使用的算法+签名密钥
//调用compact()方法将三部分拼接起来并用'.'分隔
.compact();
System.out.println("生成的JWT:" + jwtToken);
}
/**
* 解密JWT获取信息的测试方法
*/
@Test
public void parse(){
//jwt方法加密出来的密钥
String jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiLjI5LiIsInJvbGUiOiJhZG1pbiIsInN1YiI6ImFkbWluLXRlc3QiLCJleHAiOjE3MTA2MDUyMjYsImp0aSI6IjFmNTkwNmNjLWQ4MWMtNGQ0MS1hYmJiLWY2M2NkZTg5OTM0ZSJ9.sgkpuvWTyzwDbwmUeKjQt2IuuL2zG0NKrQofdItbBAU";
JwtParser parser = Jwts.parser();//获取JWT解密工具
Jws<Claims> claimsJws = parser.setSigningKey(signature).parseClaimsJws(jwtToken); //根据签名密钥对JWT进行解密
Claims claims = claimsJws.getBody();
//获取解密出来的载荷内容
System.out.println("用户名:" + claims.get("name") + "\n"
+ "规则:" + claims.get("role") + "\n"
+ "主题:" + claims.getSubject() + "\n"
+ "ID:" + claims.getId() + "\n"
+ "失效时间(有效期至):" + claims.getExpiration()
);
}
}
5. 封装一个实用的JWT工具类
工具类JwtUtil.java
:
/**
* @author .29.
* @create 2024-03-16 0:29
* @description 生成JwtToken, 获取JwtToken中加密的信息, 判断JwtToken是否合法
*
*/
@Configuration
public class JwtUtil {
//创建默认的密钥与加密算法,提供给空参构造器调用
private static final String defaultBase64EncodingSecretKey = "eb^29*be";
private static final SignatureAlgorithm defaultSignatureAlgorithm = SignatureAlgorithm.HS256;
//空参构造器,内部调用有参构造器传入默认的密钥、加密算法
public JwtUtil(){
this(defaultBase64EncodingSecretKey, defaultSignatureAlgorithm);
}
private final String base64EncodingSecretKey;//密钥
private final SignatureAlgorithm signatureAlgorithm;//加密算法
//有参构造器,创建对象并传入参数为成员变量base64EncodingSecretKey与signatureAlgorithm赋值
public JwtUtil(String base64EncodingSecretKey, SignatureAlgorithm signatureAlgorithm){
this.base64EncodingSecretKey = Base64.encodeBase64String(base64EncodingSecretKey.getBytes());;
this.signatureAlgorithm = signatureAlgorithm;
}
/**
* 生成jwtToken的方法,jwtToken中包含了三部分:Header、PayLoad、Signature
* - Header:
* 当前字符串的类型,一般是"JWT"
* 使用的加密算法,可以是"HS256"或其他算法
* - Payload:
* 一般有四种常见的标准字段:
* jat:签发时间,即jwt的生成时间
* jti:JWT的唯一标识
* iss:签发人,一般是username或userId
* exp:过期时间
* - Signature:签名
* @param issuer
* @param ttlMillis
* @param claims
* @return
*/
public String encoding(String issuer, long ttlMillis, Map<String, Object> claims){
// iss签发人,ttlMillis生存时间,claims是指还想要在jwt中存储的一些非隐私信息
if(claims == null){
claims = new HashMap<>();
}
long nowMillis = System.currentTimeMillis();
JwtBuilder builder = Jwts.builder()
//JWT唯一标识
.setId(UUID.randomUUID().toString())
//签发人,也就是JWT是给谁的
.setSubject(issuer)
//载荷部分
.setClaims(claims)
//签发时间
.setIssuedAt(new Date(nowMillis))
//生成jwt使用的加密算法、密钥
.signWith(signatureAlgorithm, base64EncodingSecretKey);
//如果生存时间大于0,过期时间就是当前时间+生存时间
if(ttlMillis > 0){
long exp = nowMillis + ttlMillis; //过期时间
Date date = new Date(exp); //封装为Date对象
builder.setExpiration(date); //设置jwt的过期时间
}
return builder.compact(); //这个方法将三部分用符号'.'连接,生成jwt,
}
/**
* 解密jwtToken,并获取jwt载荷内容的方法。
* Claims就是一个map,里面包含了jwt载荷部分的所有键值对
* @param jwtToken
* @return
*/
public Claims decoding(String jwtToken){
JwtParser parser = Jwts.parser();
Jws<Claims> claimsJws = parser.setSigningKey(base64EncodingSecretKey).parseClaimsJws(jwtToken);
return claimsJws.getBody();
}
// 判断jwtToken是否合法
public boolean isVerify(String jwtToken) {
// 这个是官方的校验规则,这里只写了一个”校验算法“,可以自己加
Algorithm algorithm = null;
switch (signatureAlgorithm) {
case HS256:
algorithm = Algorithm.HMAC256(Base64.decodeBase64(base64EncodingSecretKey));
break;
default:
throw new RuntimeException("不支持该算法");
}
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(jwtToken);
// 校验不通过会抛出异常
// 判断合法的标准:1. 头部和荷载部分没有篡改过。2. 没有过期
return true;
}
}