保存用户登录状态之Session和JWT
HTTP协议
我们知道HTTP协议是一种无状态的协议(服务器端不知道前后两次请求是否来自于同一个浏览器。所以,用户在第一次请求中登录后再在同一个浏览器中发第二次请求时,服务器并不知道这个用户已经登录了)。为了解决这个问题,就需要用一种机制来保存用户的登录状态/登录信息,常用的机制有Session和JWT.
Session
ASP.NET, ASP.NET MVC和ASP.NET MVC Core都支持用Session用保存用户的登录状态。Session的一种简单实现方式为:用户登录后会在服务器生成一个Session,每个Session里面包含SessionId(用来唯一标识一个Session)和对应的用户信息(如用户名,邮箱,电话等),所有的Session保存在内存的一块区域中。返回请求响应的时候,把SessionId包含到Cookie中,并把Cookie随着HTTP响应发给浏览器。用户下次发出请求的时候,会把包含了SessionId的Cookie发送给服务器,服务器根据Cookie里的SessionId,就知道这个用户已经登录了;根据SessionId和用户信息的对应关系,也可以拿到该用户的用户信息。
Session带来的两个问题:
问题1:在分布式集群架构中(例如当前流行的微服务架构),用户登录后就不能把Session保存到处理登录请求的那台服务器的内存里了,因为下次请求可能是由其它服务器来处理的。为了解决这个问题,可以增加一台Redis或者Memcached中心状态服务器,用来集中保存用户的登录状态。但这样做,相比从内存读取登录状态,性能会更低。
问题2:在分布式集群架构中,客户端可能是微信小程序,也可能是App,这样的客户端不支持或者不方便使用Cookie。
JWT
JWT是一段经过Base64编码后生成的字符串,人看不懂,要经过Base64解码才能看得懂,如下图所示:
登录到https://jwt.io,把JWT字符串拷贝进去,可以立即看到JWT各个部分的内容,也可以用代码来解码JWT的Header和Payload部分(解码前需要先验签以防止JWT被客户端篡改)。
JWT由Header(头部), Payload(负载), Signature(签名)三部分组成,如下图所示:
头部包含的是签名算法。
负载包含了用户名,过期时间等信息。你可以自己指定包含哪些信息。
签名里面是用签名算法生成的签名字符串。
用户成功登录后,服务器端会生成JWT并返回给客户端,客户端下次发送请求给服务器的时候会在请求头的Authorizaiton部分包含JWT(格式为Bearer JWT)。服务器收到请求后,会先把用户提交的JWT中的Header和Payload取出来,然后用服务器才知道的密钥按如下方式生成签名,并把生成的签名字符串和JWT中的签名字符串做比较,如果相等则认为用户已经登录,然后才会去拿Payload里面的用户信息。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret )
由于JWT是保存在客户端,所以不会存在Session在分布式架构中出现的两个问题。
因为服务器每次都会检查签名,所以防止了JWT在客户端被篡改。