作者 青鸟

本文主要说明两个问题:1. JWT和session的区别 2. Redis来实现分布式session

什么是session

我们知道HTTP请求是一种无状态的请求,每一次请求都无状态。当一个用户通过用户名和密码登录了之后,他的下一个请求不会携带任何状态,应用程序无法知道他的身份,那就必须重新认证。因此我们希望用户登录成功之后的每一次http请求,都能够保存他的登录状态。
为此,我们引入了session机制。session可以暂时的保存会话状态来解决HTTP的无状态问题。

JWT和session的区别

session的具体实现方法

  1. 用户输入其登录信息

  2. 服务器验证信息是否正确,并创建一个session,然后将其存储在数据库中

  3. 服务器为用户生成一个Cookie,并且会为这个Cookie指定生命周期和对应的path和Domain,在返回的请求中,会在请求头中带上一个新的请求头Set-Cookie这样一个头,浏览器接收到请求后,会自动保存下里面的Cookie,这个过程中,前端不需要做任何事情,浏览器会自动处理,同时前端也看不到相应的Cookie,这是由于同源策略带来的保护。

  4. 在后续请求中,浏览器检测到用户往这个Cookie指定的Domain下的path发送请求时会自动在请求头中带上Cookie,这个过程也不需要前端的参与,后端会根据数据库验证Cookie,如果有效,则接受请求。

  5. 一旦用户注销应用程序,会话将在客户端和服务器端自动被销毁

很多很多的文章,尤其是csdn上,关于session的说法十分错误,是将session和jwt混淆了,session是不需要前端去做什么配置的,这一切浏览器会做好。要做的只有处理好浏览器的同源策略的问题。

JWT的具体实现方法

  1. 用户输入其登录信息

  2. 服务器验证信息是否正确,并返回已签名的token

  3. token储在客户端,例如存在local storage或Cookie中
    之后的HTTP请求都将token添加到请求头里

  4. 服务器解码JWT,并且如果令牌有效,则接受请求

  5. 一旦用户注销,令牌将在客户端被销毁,不需要与服务器进行交互的一个关键是,令牌是无状态的。后端服务器不需要保存令牌或当前的记录。

JWT的组成原理。一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名,这三个部分都是json格式。头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。载荷可以用来放一些不敏感的信息。最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。加密后的内容也是一个字符串,最后这个字符串就是签名,把这个签名拼接在刚才的字符串后面就能得到完整的jwt。header部分和payload部分如果被篡改,由于篡改者不知道密钥是什么,也无法生成新的signature部分,服务端也就无法通过,在jwt中,消息体是透明的,使用签名可以保证消息不被篡改。

两者的区别

基于session和基于jwt的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而jwt是保存在客户端的。

JWT在服务端不需要存储,服务端根据前端传回的token进行解密比对处理即可。

session是在用户登录之后,给浏览器返回一个Cookie,另外在服务器端同时存储sessionid及必要的用户信息,在用户使用cookie把sessionid传回服务端,服务端基于sessionid去数据库查询,若查到则表示用户还在活跃期,同时也可以获取到存储的必要用户信息。

注意很多很多的博客错误的认为,session在本地的控制台中的application中认为session是能被看到的

这个说法是十分错误的,seesion是被浏览器储存的,不一定会被看控制台看到,这里的原因是同源策略问题,开发者认为被前端看到是不安全的,比如网络脚本这些,这一些可能会窃取session信息。

我们只需要在控制台中查看实际请求,观察请求头中是不是带有Cookie这个标头,即可判断是不是Session有没有使用成功。

这里不详细地讨论JWT和session的的优缺点,感兴趣的可以自行百度。

注意一点,很多人喜欢将cookie和session来对比,但是这没有任何意义,cookie是一种储存机制,而它只是保存会话的工具,这种比较没有意义。跟cookie比较的应该是local storage。和session比较的应该是JWT的。

Redis实现分布式session

在web开发中,我们会把用户的登录信息存储在session里。而session是依赖于cookie的,即服务器创建session时会给它分配一个唯一的ID,并且在响应时创建一个cookie用于存储这个sessionId。当客户端收到这个cookie之后,就会自动保存这个sessionId,并且在下次访问时自动携带这个sessionId,届时服务器就可以通过这个sessionId得到与之对应的session,从而识别用户的身份。在平常的开发中,我们都是用数据库来储存下用户的登录信息,在将他们发来的HTTP请求中的sessionId和数据库中的进行对比。

这时候就会遇到一个问题。假如我们采用的是分布式部署的方式即将应用程序部署在多台服务器上,并通过nginx做统一的请求分发,从而来做负载均衡。而服务器与服务器之间是隔离的,它们的session是不共享的,这就存在session同步的问题了。即如果客户端第一次访问服务器,请求被分发到了服务器A上,则服务器A会为该客户端创建session。如果客户端再次访问服务器,请求被分发到服务器B上,则由于服务器B中没有这个session,所以用户的身份无法得到验证,从而产生了不一致的问题。

我们应当知道,我们使用session来保存的用户信息本质上是为了完成保存和验证。

这时候,我们便有了一种解决办法:

第一是创建令牌,就是在用户初次访问服务器时,给它创建一个唯一的身份标识,并且使用cookie封装这个标识再发送给客户端。那么当客户端下次再访问服务器时,就会自动携带这个身份标识了,这和sessionId的道理是一样的,只是改由我们自己来实现了。在正常的session中,我们会使用现成的工具,现在,我们自己来实现。另外,在返回令牌之前,我们需要将它存储起来,以便于后续的验证。而这个令牌是不能保存在服务器本地的,因为其他服务器无法访问它。因此,我们可以将其存储在服务器之外的一个地方,那么Redis便是一个理想的场所。

第二是验证令牌的程序,就是在用户再次访问服务器时,我们获取到了它之前的身份标识,那么我们就要验证一下这个标识是否存在了。验证的过程很简单,我们从Redis中尝试获取一下就可以知道结果。

这就是用Redis来解决分布式session的问题



参考文章:

基于jwt和session用户认证的区别和优缺点

如何利用Redis实现分布式Session?

浏览器的同源策略

Gin框架之Session的使用(详解)

Gin框架使用Session以及基于redis实现分布式session & GORM操作MySQL