JWT 和 Session的区别
Authentication 和 Authorization
Authentication:用户认证,验证用户的身份。
Authorization:授权,指确认身份后提供操作权限。
http协议是无状态的,每一次请求都无状态。
当一个用户通过用户名和密码登录了之后,他的下一个请求不会携带任何状态,应用程序无法知道他的身份,那就必须重新认证。
因此我们希望用户登录成功之后的每一次http请求,都能够保存他的登录状态。
目前主流的用户认证方法有两种方式:
- 基于token
- 基于session
基于session的用户认证
- 用户登录
- 服务器验证信息并创建一个 session 存储在数据库中
- 服务器为用户生成一个 sessionId,将具有 sesssionId 的 Cookie 放置在用户浏览器中
- 后续请求中根据数据库验证sessionID
- 用户注销,会话在客户端和服务器端都被销毁
基于token(令牌)的用户认证
最常用的是 JSON Web Token(JWT)
- 用户登录
- 服务器验证信息并返回已签名的 token 存储在客户端中(存在local storage 或 cookie中)
- 之后的HTTP请求都将token添加到请求头里
- 服务器解码JWT,并且如果令牌有效,则接受请求
- 用户注销,令牌在客户端被销毁,不需要与服务器进行交互一个关键是,令牌是无状态的(后端服务器不用保存令牌或当前session的记录)
JWT
jwt的认证原理:
一个jwt实际上就是一个字符串,由三部分组成(json格式):
头部(header)
用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。{"typ": "JWT", "alg": "HS256"}
这是一个JWT,所用的签名算法是HS256算法。载荷(payload)
载荷可以用来放一些不敏感的信息。{"iss": ohn Wu JWT", "iat": 1441593502, "exp": 1441594722, "aud": "www.example.com", "sub": "jrocket@example.com", "from_user": "B", "target_user": "A"}
这里面的前五个字段都是由JWT的标准所定义的。
iss: 该JWT的签发者
sub: 该JWT所面向的用户
aud: 接收该JWT的一方
exp(expires): 什么时候过期,这里是一个Unix时间戳
iat(issued at): 在什么时候签发的 把头部和载荷分别进行Base64编码之后得到两个字符串,然后再将这两个编码后的字符串用英文句号.连接在一起(头部在前),形成新的字符串:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn签名(signature)
最后将上面拼接完的字符串用HS256算法进行加密。
在加密的时候需要提供一个密钥(secret)。
加密后的内容也是一个字符串,最后这个字符串就是签名,把这个签名拼接在刚才的字符串后面就能得到完整的jwt。
header部分和payload部分如果被篡改,由于篡改者不知道密钥是什么,也无法生成新的signature部分,服务端也就无法通过
在jwt中消息体是透明的,使用签名可以保证消息不被篡改。eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn
区别和优缺点
主要区别:
用户的状态保存的位置,session保存在服务端的,而jwt保存在客户端的。
jwt的优点:
- 可扩展性好 应用程序分布式部署的情况下,session需要做多机数据共享,通常可以存在数据库或者redis里面。而jwt不需要。
- 无状态 jwt不在服务端存储任何状态。RESTful API的原则之一是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。
用户的认证状态引入这种附加影响,这破坏了这一原则。
另外jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 JWT,可以降低服务器查询数据库的次数。
jwt的缺点:
- 安全性
由于jwt的payload使用base64编码,没有加密,因此jwt中不能存储敏感数据。
而session的信息是存在服务端的,相对来说更安全。 - 性能
jwt太长。由于是无状态使用JWT,所有的数据都被放到JWT里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage里面。
并且用户在系统中的每一次http请求都会把jwt携带在Header里面,http请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用jwt的http请求比使用session的开销大得多。 - 一次性
无状态导致了jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。 - 无法废弃 一旦签发一个jwt,在到期之前就会始终有效,无法中途废弃。
例如你在payload中存储了一些信息,当信息需要更新时,则重新签发一个jwt,但是由于旧的jwt还没过期,拿着这个旧的jwt依旧可以登录,那登录后服务端从jwt中拿到的信息就是过时的。
为了解决这个问题就需要在服务端部署额外的逻辑,例如设置一个黑名单,一旦签发了新的jwt,那么旧的就加入黑名单(比如存到redis里面),避免被再次使用。 - 续签 如果你使用jwt做会话管理,传统的cookie续签方案一般都是框架自带的,session有效期30分钟,30分钟内如果有访问,有效期被刷新至30分钟。
一样的道理,要改变jwt的有效时间,就要签发新的jwt。最简单的一种方式是每次请求刷新jwt,即每个http请求都返回一个新的jwt。这个方法不仅暴力不优雅,而且每次请求都要做jwt的加密解密,会带来性能问题。
另一种方法是在redis中单独为每个jwt设置过期时间,每次访问时刷新jwt的过期时间。 - 可以看出想要破解jwt一次性的特性,就需要在服务端存储jwt的状态。但是引入 redis 之后,就把无状态的jwt硬生生变成了有状态了,违背了jwt的初衷。而且这个方案和session都差不多了。
适合使用jwt的场景:
- 有效期短
- 只希望被使用一次
比如,验证码就适合使用jwt。
而由于jwt具有一次性的特性。
单点登录和会话管理非常不适合用jwt,如果在服务端部署额外的逻辑存储jwt的状态,那还不如使用session。基于session有很多成熟的框架可以开箱即用,但是用jwt还要自己实现逻辑。







