https://www.bilibili.com/video/BV1i54y1m7cP
文章目录
1. 什么是JWT
- jwt(json web token)是一种开放标准(rfc 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全的传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名
- 通俗来讲,jwt就是通过JSON形式作为WEB应用中的令牌,用于在各方之间安全的将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。
2. jwt能做什么
- 授权
这是使用jwt最常见的方案。一旦用户登录,每个后续请求将包含JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。 - 信息交换
jwt是在各方之间安全传输信息的好办法。因为可以对JWT进行签名,所以可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此还可以验证内容是否遭到篡改。
3. 为什么是JWT
传统的基于session认证
因为http本身是一种无状态的协议,我们并不能知道是哪一个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器端存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,以便引用识别用户。
暴露问题:
- 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以便用户下次请求的鉴别,通常而言session是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大
- 用户认证之后,服务端做认证记录。如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力,这也意味着限制了应用的扩展能力。
- 因为是基于cookie来识别用户的,当cookie被截获,用户很容易受到2跨站请求伪造的攻击。
- 在前后端分离系统中就更加痛苦:前后端分离([用户]< == > [前端web] < == > [代理层] < == > [后端应用] )在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session每次携带sessionid到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是CSRF(跨站伪造请求攻击),session是基于cookie进行用户识别的,cookie如果被截获,用户很容易受到跨站请求伪造的攻击。还有就是sessionid是一个特征值,表达的信息不够丰富。不容易扩展。入股后端应用是多节点部署,那么就要实现session共享机制。不方便集群应用。
jwt认证流程
- 首先,前端通过web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密传输(https协议),从而避免敏感信息被嗅探。
- 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个token。
- 后端将jwt字符串作为登陆成功的返回结果返回给前端。前端将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
- 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决xss和xsrf问题)
- 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查token是否过期;检查token的接收方是否是自己(可选)。
- 验证通过后后端使用JWT包含的用户信息进行其它逻辑操作,返回相应结果。
jwt组成
jwt分为三个部分
- 第一部分 header :标记使用什么算法 (HS256/RSA256)
- 令牌的类型和所使用的签名算法,例如HMAC SHA256或RSA。
- {“alg”:“HS256”,“typ”:“JWT”}
- 第二部分 PayLoad (载荷)
jwt存放的数据 - 第三部分 PayLoad 采用MD5加密之后的签名值
! jwt不要存放重要数据
Base64.Encode(header). Base64.Encode(PayLoad ).签名值
private static final String SIGN_KEY = "mayikt";
@Test
public void test1(){
JSONObject header = new JSONObject();
header.put("alg","HS256");
JSONObject payLoad = new JSONObject();
payLoad.put("phone","18611011111");
String payLoadStr = payLoad.toJSONString();
//payLoad实现MD5加密
String sign = DigestUtils.md5DigestAsHex((payLoadStr+SIGN_KEY).getBytes());
Base64.Encoder encoder = Base64.getEncoder();
String jwt = (String) encoder.encodeToString(header.toJSONString().getBytes())+"."+
encoder.encodeToString(payLoadStr.getBytes())+"."
+sign;
System.out.println(jwt);//eyJhbGciOiJIUzI1NiJ9.eyJwaG9uZSI6IjE4NjExMDExMTExIn0=.badce2fbf728c40222e7fa75bcfa9e54
}
生成的jwt可在https://jwt.io/验证
验证签名位true的情况下就可以获取payload数据,否则jwt无效。
优缺点
优点:
- 简洁:数据量小,传输速度快
- 自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库
- jwt客户端就可验证,减轻服务端的压力,适合分布式微服务
- 以JSON加密的形式保存在客户端,跨语言,原则上任何web形式都支持。
- jwt查询效率比token高
- 不容易被客户端篡改数据
缺点:
- 一旦生成好一个jwt,后期比较难使它失效
- 如果payload数据过多,占用服务器带宽资源。
建议在jwt的payload数据中增加时间戳,设置有效期
demo
- 引入依赖 com.auth0 java-jwt
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
- 生成token:
//生成时间戳
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,90);
//生成令牌
String token = JWT.create()
.withClaim("user","yz")
.withExpiresAt(instance.getTime()) //设置过期时间
.sign(Algorithm.HMAC256("token!Q2W#E$RW"));//设置签名 保密 复杂
//输出令牌
System.out.println(token);//eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDIxNzg3OTYsInVzZXIiOiJ5eiJ9.PavrIse8cAsM3bW4roGgkY8kHTAqBwiV8kZRT1K4eu8
解析结果:
header:{
"typ": "JWT",
"alg": "HS256"
}
payload: {
"exp": 1602178796,
"user": "yz"
}
- 根据令牌和签名解析数据
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("user: " + decodedJWT.getClaim("user").asString());
//新增.withClaim("id",1)
System.out.println("id: " + decodedJWT.getClaim("id").asInt());
System.out.println("过期时间:" + decodedJWT.getExpiresAt());
整合spring boot
封装工具类JWTUtils
方法 getToken
传入Map<String,Object> map
foreach map -> withClaim
验证token方法 verify
获取token信息方法:getTokenInfo(String token,String key) 或直接返回DecodedJWT
连接数据库