1、什么是token,解决了什么问题?
token 就是常说的 “令牌”,本质上是全局唯一的字符串,用来唯一识别一个客户端,解决了session依赖单个web服务器的问题。单体应用时,用户的会话信息保存在session中,session存在于服务器端的内存中,但是对于使用服务器集群的情况,比如用nginx做负载均衡,采用轮询的方式访问服务器集群时,就会出现一种情况:用户A登录了服务器1,此时用户的session保存在了服务器1中,但是第二次请求被分配到了服务器2,由于服务器2没有用户的session信息,所以用户A就还要再次登录,这样用户的体验肯定不好。
对于 cookie和session的机制,在请求中根据cookie中的jesessionid来自动找到服务器端的session,从而从session中取出当前用户的会话信息。
Session session = request.getSession();// 获取session
session.getAttribute("user") // 获取session中的用户信息
我们可以模拟cookie和session的这种机制:
1)cookie中是根据jesessionid来找到服务器端的session的,jesessionid就是一个全局唯一的随机字符串,我们也可以生成一个全局唯一的字符串比如使用UUID或时间戳加随机字符串的形式;
2)web服务器在内存中存储所有的session,每个session都有一个唯一的id标识,value就是session本身,session里面又有好多键值对。数据在内存中、key-value形式的、value里面又有好多键值对,这简直就是对redis的哈希表十分准确的描述啊,所以我们可以使用redis的哈希类型来模型服务器端session。
3) 有了客户端的随机字符串,有了服务器端的会话信息存储, 接下来就是匹配:
cookie和session机制中是根据cookie中的jesessionid来自动找到服务器端的session,说是自动查找,其实是我们调用了他封装好的方法request.getSession()获取的,这个request中就包含了本次请求的所有信息,而调用的getSession()方法中,肯定做了这件事——获取请求头中cookie的jesessionid的值,根据这个值到服务器的内存中找到对应的session并返回。
我们可以在用户第一次请求该web服务器时或是用户登录该web服务器时,生成一个全局唯一的token返回给前端存储,同时将该用户信息存到redis中并设置有效期,之后每次请求中都在请求头中带着这个token,服务器端根据这个token到redis中查找对应的用户信息,即得到了我们所说的 “session”。
流程:
1)用户使用用户名密码请求服务器(这个过程的一般是HTTP的POST请求,也可用SSL加密的https协议);
2)服务器进行验证用户的信息;
3)服务器通过验证返回发给用户一个token(并可以将用户的id等其他信息作为 JWT Payload(负载))将其与头部分别进行 Base64编码拼接后签名形成一个JWT(Token),形成的JWT字符串(token)就是header.payload.signature组成的编码后的字符串的形式;
4)客户端存储token值,并在每次请求的时候附带上这个token值(前端会将JWT(token)字符串放入Header中的 Authorization位);
5)服务器验证token,并返回数据(比如验证JWT的有效性:签名是否正确、token是否过期、检查token的接收方是否是自己(用户客户端))
2、JWT的组成(令牌组成)
JWT(token)是由三段信息构成,Header.PayLoad.Signature;
2.1 令牌组成
- 标头 (Header)
- 有效载荷 (PayLoad)
- 签名 (Signature)
2.2 Header
{
"alg":"HS256",
"typ":"JWT"
}
2.3 PayLoad
令牌的第二部分是有效负载,其中包含声明(即有关实体通常是用户的一些不太敏感的信息【如密码等】,也是可以通过Base64编码,正因为这种不太安全,所以负载中尽量不会存放敏感的数据)
{
“uid” : "123456",
"uname":"mary"
}
2.4 Signature
标头和负载都可以通过Base64编码的,前端也可以解码获取真实信息,而签名是需要编码后的Header和PayLoad 以及我们提供的一个密钥,然后 使用标头中的签名算法如“HS256”进行签名,签名的作用是保证JWT没有被篡改过。
1.var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
2.var signature = HMACSHA256(encodedString, 'secret');
注意: secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
实际上就是对头部以及负载内容进行签名,防止内容被篡改(因为Base64可以被解码)。如果有人对头部和负载内容解码修改后,再进行编码,签名形成新的 JWT那么服务器端就会判断出新的头部和负载形成的签名和 JWT(Header.PayLoad.Signature) 本身带的签名是不一致的。