在登录一个网站进行访问时由于HTTP协议是无状态的就是说一次HTTP请求后他就会被销毁,比如我在www.a.com/login里面登录了,然后你就要访问别的了比如要访问www.a.com/index但是你访问这个网站你就得再发一次HTTP请求,至于说之前的请求跟现在没关,不会有任何记忆,这次访问会失败,因为无法验证你的身份。所以你登录完之后每次在请求上都得带上账号密码等验证身份的信息,但是你天天这么带,那太麻烦了。那还可以这样,把我第一次登录的信息状态都放在数据库里,下次我一访问,我查一下数据库就知道我登没登陆了,但是频繁查找数据库会给后台服务器造成非常大的压力所以就出现了Cookie,第一次登录就会返回一个Cookie,将一些简单地信息放在Cookie里返回给客户端,然后在客户端保存,每个域名下对应有一堆Cookie,下次我带Cookie来访问就行了。这样做也行但是Cookie很容易被篡改放在客户端并不安全,而且Cookie多了会无形的增加客户端与服务端的传输数据量。所以Session就出现了,Session放在后台服务器,将SessionID返回给客户端作为Cookie的值下次我带Cookie过来通过SessionID来查找Session中的一些登录或其他信息就行了。这样做也挺好。但是如果是集群环境下,那就不行了Session不能跨域也就是说你用www.baidu.com下的SessionID访问www.bilibili.com下的Session是不行的为了解决这个问题我们还得将Session在每台服务器上进行同步这也是一笔巨大的开销。
这个时候Token出现了Token本质上也是一个类似于SessionID的东西,在服务器后端会有一个Redis数据库,将token和对应的用户信息存放在redis中即可。key为token的值,value为userid。这样的话不管什么服务我只要返回一个token就行了下次客户端带着token传给服务器就行了。服务器根据token访问到redis然后查找到useid就能确认访问这的身份。但是如果Token数量过多,频繁访问后台redis也会给服务器造成巨大压力。
具体过程如图所示
前面说了使用Token可能造成后台服务器压力过大,于是又出来一种令牌JSON Web Token(JWT),JWT被用来在身份提供者和服务提供者之间传递被认证的身份信息,以便从服务器获取资源。
1.Header(头) 作用:记录令牌类型、签名算法等
2.Payload(有效载荷)作用:
携带存放的数据 用户名称、用户头像之类 注意敏感数据
标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
3.Signature(签名)作用:防止Token被篡改、确保安全性 例如 计算出来的签名,一个字符串
载荷可以不放userid如果你不需要查询数据库的话就不用如果需要最好加上。
那么如何实现一个JWT呢,JWT就是就是由头部,有效载荷,签名拼接起来的字符串。JWT是JSON Web Token所以头部载荷都是json格式,以key-value形式存储。具体如下
String signKey = "wjzmvp";
JSONObject payLoad = new JSONObject();
JSONObject header = new JSONObject();
header.put("Alg","HS256");
payLoad.put("userId","35");
payLoad.put("name","wjz");
payLoad.put("age","20");
payLoad.put("other","会喝药没有弟弟");
String jwtHeader = Base64.getEncoder().encodeToString(header.toJSONString().getBytes());
String jwtpayLoad = Base64.getEncoder().encodeToString(payLoad.toJSONString().getBytes());
String sign = DigestUtils.md5Hex(payLoad.toJSONString() + signKey);
String JWT = jwtHeader + "." + jwtpayLoad + "."+sign;
System.out.println(JWT);
创造好JSON格式的头部和载荷,然后分别对他们以Base64形式进行编码(注意不是加密),然后再设置一个签名key将这个签名key和载荷的JSON字符串相拼接再用md5加密,形成签名然后再将编码好的头部和载荷与这个签名通过"."连接形成JWT。
那为什么说JWT就比之前的Token要好呢,因为之前的Token将用户信息保存在后台服务器上,确实保证了用户信息的安全,同时在不同服务之间也可以进行跨域访问,但是当有过多的Token同时进行访问时会给后台服务器造成巨大压力。JWT将用户的一些信息放在载荷里,也就是说他把信息存储在了客户端,它没有被篡改的风险,因为他在访问后台时会带着JWT,服务器要对这个JWT进行检验。
//解密
String payLoadStr = new String(Base64.getDecoder().decode(JWT.split("\\.")[1].getBytes()),"UTF-8");
String jwtSign = JWT.split("\\.")[2];
System.out.println(DigestUtils.md5Hex(payLoadStr + signKey).equals(jwtSign));
他会将载荷进行解码,然后再加上这个签名key进行加密与传回来的jwt的签名进行比较如果是正确的说明没有被篡改,如果是错的说明它被篡改了。由于signKey是存放在服务器端的,所以比较安全只要JWT被篡改就会立刻发现。载荷中尽量不要放密码之类的敏感信息,如果你要是想查数据库的话带上用户名就行了,后台会将payLoad还原成String型的数据然后拿出用户名对后台进行访问。
jwt一旦生成很难注销
1.清除Cookie但是服务端还会保存。
2.失效时间设置的短一点。让他自己失效。