JWT 官网:https://jwt.io/introduction/ JWT 在线校验:https://jwt.io/#debugger-io 推荐的测试代码:#Test
什么是 JWT?
JSON WEB TOKEN,它定义了一种紧凑且自包含的方式,用于将信息作为 JSON 对象安全地在各方之间传输信息。此信息可以验证和信任,因为它是数字签名。JWT 可以使用密钥(使用HMAC算法)或使用 RSA 或 ECDSA 进行公钥/私钥对进行签名。
它有什么作用呢?(抄自JWT官网)
- 授权:这是使用 JWT 的最常见方案。用户登录后,每个后续请求都将包括 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销小,并且能够轻松地跨不同的域使用。
- 信息交换:JSON 网络令牌是各方之间安全传输信息的一种好方式。由于可以对JWT进行签名(例如,使用公钥/私钥对)可以确定发件人就是他们说的。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
我们一般只去API使用 授权、校验
为什么使用 JWT
- 解决Session的内存占用问题 (存储于客户端)
- 解决各个服务端 Session共享问题
JWT 认证流程
生成Token
- 客户端携带自己的信息 请求后台认证端口
- 后台核对客户端提交的信息,将用户信息作为JWT 令牌的负载(Payload)与头部进行Base64 编码的拼接,形成一个Token。格式例如Head.Payload.Singurater 以 “.” 进行拼接,这里要看仔细
- Token生成后,将其作为字符串以登录成功的返回结果返回给前端。
- 前端将后台生成的结果 存储在 loacalStorage 或者 sessionStorage上 (如果,用户退出登录,可以选择删除浏览器的响应信息即可)
使用 Token
- 后续用户请求,前端需要将JWT 放入http请求的 Header 中的 Authorization 位 (可以解决 XSS 和 XSRF 问题)
- 后台每次接受到请求,都要检查 JWT 是否存在,并验证有效性(是否有效、是是否过期等等),通过验证,就可以正常访问了。
JWT 的优势
- 简洁 :可以通过http请求中的head 放入jwt ,其数据量小,传输速度块
- 自包含:负载中,可以存储一部分信息,可以减少数据库的查询次数
- 跨语言:字符串格式,任何web形式 都支持
- 使用与微服务,不需要考虑共享问题
JWT 的结构组成 (部分抄自官网)
- 头部(Header)
- 负载(Payload)
- 签名(Signature)
头
标头通常由两部分组成:令牌的类型(即 JWT)和正在使用的签名算法,如 HMAC SHA256 或 RSA。
例如:
代码语言:javascript复制{
"alg": "HS256",
"typ": "JWT"
}
然后,此 JSON编码为 Base64Url,以形成 JWT 的第一部分。
负载
令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常为用户)和其他数据的语句。有三种类型的索赔:已登记、公共和私人索赔。
- 已注册声明:这些是一组预定义声明,不是强制性的,但建议提供一组有用的、可互操作的索赔。其中一些是:iss(发行人)、exp(到期时间)、子(主题)、aud(访问者)和其他。请注意,声明名称只有三个字符,只要 JWT 是紧凑的。
- 公共声明:这些可以由使用JWT的人可以当即定义。但是,为了避免冲突,应在IANA JSON Web 令牌注册表中定义它们,或定义为包含抗冲突命名空间的 URI。
- 私人声明:这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,它们既不是已注册的,也不是公开声明。
示例有效负载可能是:
代码语言:javascript复制{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对有效负载进行 Base64Url编码,以形成 JSON Web 令牌的第二部分。
请注意,对于已签名的令牌,此信息虽然可防止篡改,但任何人都可以阅读。除非对 JWT 进行加密,否则不要将机密信息放在 JWT 的有效负载或标头元素中。
签名
要创建签名部分,您必须使用编码标头、编码有效负载、机密、标头中指定的算法,并签名。
例如,如果要使用 HMAC SHA256 算法,将采用以下方式创建签名:
代码语言:javascript复制HMACSHA256(
base64UrlEncode(header) "."
base64UrlEncode(payload),
secret)
签名用于验证邮件未随之更改,对于使用私钥签名的令牌,它还可以验证 JWT 的发件人是否为它所说的发件人。
放在一起
输出是三个 Base64-URL 字符串,由点分隔,这些点可以在 HTML 和 HTTP 环境中轻松传递,但与基于 XML 的标准(如 SAML)相比,更紧凑。
下面显示了一个 JWT,它具有以前的标头和有效负载编码,并且它使用机密进行签名。
如果要使用 JWT 并付诸实践,可以使用 jwt.io器解码、验证和生成 JWT。
JAVA 使用 JWT
生成令牌
导包
代码语言:javascript复制 <dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.13.0</version>
</dependency>
调用 JWT.create() 方后面的链式调用,添加相关参数
代码语言:javascript复制 // 生成Token
@Test
public void createJwt(){
HashMap<String,Object> hashMap = new HashMap<>();
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,100); // 设置超时时间 100s
String token = JWT.create()
.withHeader(hashMap) // Header
.withClaim("username", "张三") // Payload
.withClaim("age", 20) // Payload
.withExpiresAt(instance.getTime()) // 设置超时时间
.sign(Algorithm.HMAC256("!Govbuy2021JWT~ZLK")); // 自己生成的签名,足够复杂就行,不要泄露就行
System.out.println("Token is in next line:");
System.out.println(token);
}
结果 如图
额外知识:
如果 相同的负载 字段名,例如 “username” 一个叫张三 、一个叫李四,那么就会默认使用代码顺序下面的”username”,即Claims 中 只会存在一个 “username”
验证令牌
调用 JWT 验证对象 JWTVerifier 的 verify() 方法 解析
代码语言:javascript复制 @Test
public void ValidateJwt(){
JWTVerifier build = JWT.require(Algorithm.HMAC256("!Govbuy2021JWT~ZLK")).build();
DecodedJWT verify = build.verify(""
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM2MTM3MjksImFnZSI6MjAsInVzZXJuYW1lIjoi5byg5LiJIn0.G2tQ5564EDx1V7yF4GT6tTCn5lLZHSSdOfpm3ae73w4");
// 获取令牌内 包含的信息
String username = verify.getClaim("username").asString();
int age = verify.getClaim("age").asInt();
System.out.println(username);
System.out.println(age);
}
如果令牌 超过设置的过期时间 ,就会报 令牌超时异常
代码语言:javascript复制com.auth0.jwt.exceptions.TokenExpiredException: The Token has expired on Thu Feb 18 09:38:12 CST 2021.
整合SpringBoot
创建 自己的工具类
代码语言:javascript复制import cn.hutool.core.date.DateUtil;
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.HashMap;
import java.util.Map;
/**
* @author : zanglikun
* @date : 2021/2/18 9:58
* @Version: 1.0
* @Desc : JWT生成工具。具体JWT文章请访问:
*/
public class JWTUtils {
// 定义的加密密钥
private static final String MYSign = "!HEFEI2021JWT~ZLK";
/**
* 生成 令牌
* @param map 你创建的参数集合
* @return
*/
public static String getToken(Map<String, String> map) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE,7); // 设置超时时间 7 天
System.out.println(instance.getTime());
// 创建 JWT Builder
JWTCreator.Builder builder = JWT.create();
// 设置 Payload
map.forEach((k, v) -> {
builder.withClaim(k, v);
});
// 超时时间 与 Sign
String sign = builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(MYSign));
return sign;
}
/**
* 验证 Token 的合法性 只要不抛异常,就算令牌 验证成功
* @param token 你传递的token
*/
public static void verifyToken(String token) {
JWTVerifier build = JWT.require(Algorithm.HMAC256(MYSign)).build();
DecodedJWT verify = build.verify(token);
//System.out.println("签名验证成功,会打印此话");
}
/**
* 获取 Token中 信息,以后 直接 通过 DecodedJWT 进行 .getClaim.as数据类型() 获取;
* @param token 你传递的token
* @return DecodedJWT 可用于获取数据
*/
public static DecodedJWT getInfo(String token) {
return JWT.require(Algorithm.HMAC256(MYSign)).build().verify(token);
}
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("Abc你好123","Abc你好123");
String token = JWTUtils.getToken(hashMap);
/* JWT 常用方法
JWTUtils.getInfo(token).getHeader();
JWTUtils.getInfo(token).getPayload();
JWTUtils.getInfo(token).getSignature();
*/
System.out.println(JWTUtils.getInfo(token).getClaims());
Long exp = JWTUtils.getInfo(token).getClaim("exp").asLong();
//这里使用了Hutool的工具 打印时间
System.out.println("过期时间是:" DateUtil.date(exp*1000));
}
}
未来在单体架构,就放在 拦截器 放行登录接口就行,减少验签等代码的冗余,分布式架构,就放在网关里面即可。
代码语言:javascript复制 <!--Hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.15</version>
</dependency>
// 创建JWT
@Test
public void JWT() {
Map payload = new HashMap<>();
payload.put("账号","123");
payload.put("姓名","汪顺");
// Token 有效期是15天 可不设置
payload.put("expire_time",System.currentTimeMillis() 1000 * 60 * 60 * 24 * 15);
// 生成Token并执行加密方式:hs256 密码是123456
String token = JWTUtil.createToken(payload, JWTSignerUtil.hs256("123456".getBytes()));
System.out.println(token);
}
// 验证JWT
@Test
public void UnJWT() {
// 这是我们生成的Token
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyLotKblj7ciOiIxMjMiLCLlp5PlkI0iOiLmsarpoboiLCJleHBpcmVfdGltZSI6MTYzNzU4MTIyMzQ4NH0.SWZd0KmcNkrRyDn9uTp1O3CkFaiCmSXlrf3Gyw-GYck";
try {
// 这是验证Token完整性 如果有异常,就说明Token不完整
JWTValidator.of(token).validateAlgorithm(JWTSignerUtil.hs256("123456".getBytes()));
// 判断Token是否超时
JWTPayload payload = JWTUtil.parseToken(token).getPayload();
Long nowtime = new Date().getTime();
if (Long.valueOf((Long) payload.getClaim("expire_time")) < nowtime){
System.out.println("Token超时");
int i = 1/0;
}
// 打印负载的内容
System.out.println(payload.getClaim("账号"));
System.out.println(payload.getClaim("姓名"));
System.out.println(DateUtil.date((Long) payload.getClaim("expire_time")));
}catch (Exception e){
System.out.println("Token校验不通过");
}
}
特殊说明: 解决问题的光鲜,藏着磕Bug的痛苦。 万物皆入轮回,谁也躲不掉! 以上文章,均是我实际操作,写出来的笔记资料,不会出现全文盗用别人文章!烦请各位,请勿直接盗用!