教程来源于哔哩哔哩:https://www.bilibili.com/video/BV1i54y1m7cP
官网:https://jwt.io/introduction
JsonWebToken (JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HHAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名
总而言之:JWT 就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等处理。
这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
JSON Web Token 是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
1.每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大
2.用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
3.因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
4.在前后端分离系统中就更加痛苦:如下图所示也就是说前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session 每次携带sessionid到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是CSRF〈(跨站伪造请求攻击)攻击,session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。还有就是sessionid就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。不方便集群应用。
标头(Header)
有效负荷(Payload)
签名(Signature)
因此:JWT通常如下所示:xxxxx.yyyyy.zzzzz Header.Payload.Signature
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "123456",
"name": "yanchi",
"admin": true
}
签名目的:
信息安全问题:
雁迟demo案例:https://gitee.com/panlsp/jwt/blob/master/src/test/java/com/yanchi/MyTest.java
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.18.1version>
dependency>
HashMap<String, Object> header = new HashMap<>();
Date date = new Date();
long time = date.getTime();
date.setTime(time + 60 * 1000); // 60秒过期
String token = JWT.create()
.withHeader(header) //1、标头(header)信息,不设置有默认值(参考文档)
.withClaim("id", 111) // 2、有效负荷(Payload)
.withClaim("name", "雁迟")
.withExpiresAt(date) // 指定令牌过期时间
.sign(Algorithm.HMAC256("!@PANYZ"));// 3、签名(Signature)自定义
System.out.println(token);
生成结果:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6ZuB6L-fIiwiaWQiOjExMSwiZXhwIjoxNjMxNzU1OTk5fQ.QLFv_qzDnMKn8XuqRZ-98s16IJqG73AywkvZ4JwYHYs
// 解析的签名
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!@PANYZ")).build();
// 解析的token
DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6ZuB6L-fIiwiaWQiOjExMSwiZXhwIjoxNjMxNzU1OTk5fQ.QLFv_qzDnMKn8XuqRZ-98s16IJqG73AywkvZ4JwYHYs");
// 获取 有效负荷(Payload)里面的value
System.out.println(decodedJWT.getClaim("id").asInt());
System.out.println(decodedJWT.getClaim("name").asString());
- AlgorithmMismatchException: 算法不匹配异常
- SignatureVerificationException: 签名不一致异常
- TokenExpiredException: 令牌过期异常
- InvalidClaimException: 失效的payload异常
public class JWTUtil {
// 自定义签名
private static final String SIGN = "!@YanChiSign";
/**
* 获取token令牌
* @param claim 传入payload的map集合,此方法只支持String类型。如需其他类型请自行拓展。
* @return token
*/
public static String getToken(Map<String, String> claim) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 7); // 默认7天过期
// 创建jwt builder
JWTCreator.Builder builder = JWT.create();
// payload -- 把传进来的map放入builder.withClaim()中
claim.forEach(builder::withClaim);
return builder.withExpiresAt(calendar.getTime()) // 指定令牌过期时间
.sign(Algorithm.HMAC256(SIGN));// 指定签名sign
}
/**
* 解析token令牌
* @param token 传入token字符串
* @return verify证明
*/
public static DecodedJWT getVerify(String token) {
// 解析的签名
return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
}