一,JWT简介
JWT全称叫json web token,通过数字签名的方式,以json对象为载体,在不同的服务终端之间安全的传输信息。
JWT在前后端分离系统,或跨平台系统中,通过JSON形式作为WEB应用中的令牌(token),用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中,还可以完成数据加密、签名等相关处理。
前端应用在访问后端应用时,会携带令牌(token),后端应用在接到令牌(token)后,会验证令牌(token)的合法性。从而决定前端应用是否能继续访问。JWT还可以系统之间进行信息传递,A系统通过令牌对B系统进行数据传输,在传输过程中,可以完成数据的加密,B系统拿到数据后,通过签名进行验证,从而判断信息是否有篡改。
二,JWT的应用
JWT最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含JWT,系统在每次处理用户请求之前,都要先进行JWT安全校验,通过之后再进行处理。
1、授权
这是JWT最常见方案,一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。
2、信息交换
JWT是在各方之间安全地传输信息的好方法,可以验证传输内容是否遭到篡改。
三,JWT和session的比较
session存在的问题:
1、每个用户经过我们的应用认证之后,将认证信息保存在session中,由于session服务器中对象,随着认证用户的增多,服务器内存开销会明显增大。
2、用户认证之后,服务端使用session保存认证信息,那么要取到认证信息,只能访问同一台服务器,才能拿到授权的资源。这样在分布式应用上,就需要实现session共享机制,不方便集群应用。
3、因为session是基于cookie来进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
JWT认证基于令牌,该令牌存储在客户端。但认证由服务器端进行,解决服务器内存占用问题。当用户提交用户名和密码时,在服务器端认证通过后,会生成token令牌。然后将令牌响应给客户端浏览器。客户端浏览器会在本地存储令牌。在客户端再次请求服务器接口时,每次都会携带JWT,在服务器验证通过后,再继续访问服务器资源
JWT的优势
1、简洁,可以通过URL、POST参数或Http header发送,因为数据量小,传输速度快。
2、自包含,负载(属于JWT的一部分)中包含了用户所需要的信息,不需要在服务器端保存会话信息,不占服务器内存,也避免了多次查询数据库,特别适用于分布式微服务。
3、因为token是以json加密的形式保存在客户端的,所以JWT可以跨语言使用,原则上任何WEB形式都支持。
四,JWT认证流程
1、前端将用户名和密码发送到后端服务器。后端服务器对用户名和密码验证通过后,将用户信息作为JWT Payload负载,将其与头部进行Base64编号拼接后签名,形成JWT。形成的JWT本质上就是一个形如lll.zzz.xxx的字符串。
2、后端将JWT字符串作为登陆成功的返回结果返回给客户端。前端可以将返回结果保存在localStorage,退出登陆时,前端删除保存的JWT即可。
3、前端在每次请求时将JWT放入HTTP Header中的Authorization位。
4、后端检查是否存在,如果验证JWT有效,后端就可以使用JWT中包含的用户信息。
五,JWT组成
JWT其实就是一段字符串,由标头、有效负载、签名这三部分组成,用 . 拼接
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2Mjc5NzA5NDUsInVzZXIiOiJ7XCJpZFwiOjMsXCJuYW1lXCI6XCLlkInlkIlcIixcInB3ZFwiOlwiMzMzXCJ9In0.NvGdUjFLJWj_ZzhY9Qp--NkZgK1QGQtQjiCB7lEsFTg
Header{ //标头
‘typ’:’JWT’, 表示token类型
‘alg’:’HS256’ 表示签名算法
}
它会使用 Base64编码组成JWT结构的第一部分。
Payload //有效负载 {
‘userCode’:’43435’,
‘name’:’john’,
‘phone’:'13950497865'
}
用于存储主要信息,使用 Base64编码组成JWT结构的第二部分。由于该信息是可以被解析的,所以,在信息中不要存放敏感信息。
Signature签名
前面两部分都使用Base64进行编码,前端可以解开知道里面的信息, Signature需要使用编码后的header和payload以及我们提供的一密钥,然后使用header中指定的签名算法进行签名,以保证JWT没有被篡改过。
使用Signature签名可以防止内容被篡改。如果有人对头部及负载内容解码后进行修改,再进行编码,最后加上之前签名组成新的JWT。那么服务器会判断出新的头部和负载形成的签名和JWT附带的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
六,JWT的环境搭建
在pom.xml文件中导入依赖:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.13.0</version>
</dependency>
七,JWT的创建
private static String signature = "lovo!";
public String createToken(Map<String,String> map){
Calendar c = Calendar.getInstance();
c.add(Calendar.MINUTE,30);
//header标头可不写,默认为JWT
JWTCreator.Builder builder = JWT.create();
for(String key : map.keySet()){
//payload负载,具体数据
builder.withClaim(key,map.get(key));
}
String token = builder.withExpiresAt(c.getTime())
.sign(Algorithm.HMAC256(signature));
return token;
}
八,JWT的解码
public String verify(String token,String key){
//创建解码对象
JWTVerifier jwtVerifier = JWT.require(
Algorithm.HMAC256(signature)).build();
DecodedJWT dj = jwtVerifier.verify(token);
String value = dj.getClaim(key).asString();
return value;
}
在解码过程中,如果解码失败,则抛出异常。
九,JWT的发送和接收
在应用控制器Controller中,通过响应头发送给客户端。
response.setHeader("token", JWT字符串);
客户端通过axios接收响应头,并保存在sessionStorage中。
var param = {name:"tom",pwd:"111"};
await axios.get("/user/login",{params:param}).then(e => {
var info = e.data;//响应消息体
sessionStorage.setItem("token",e.headers.token);
});
客户端再次请求,读取localStorage的JWT信息,以请求头的方式,发送给服务器
var tk = localStorage.getItem("token");
axios.get("/user/getLoginUser",
{headers:{token:tk}}).then(e => {
info = e.data;
});
服务器从请求头中得到jwt字符串,解析后,得到数据。
String value = new JWTUtil().verify(
req.getHeader("token"),"userName")