有关Cookie-Session以及JWT

107 阅读7分钟

Cookie-Session和JWT

为什么需要他们?

HTTP协议是一种无状态的协议,对于需要登录的请求,用户认证之后,下一次请求还需要再进行用户认证才可以,因为根据HTTP协议,我们并不直到是哪个用户发出的请求,所以我们需要想办法识别出用户登录过。

场景

登录之后一段时间内不需要再次重复登录。

Cookie-Session方案

传统认证方式,只适用于Web场景,不适用于APP。

认证流程

  1. 用户输入用户名、密码或者用短信验证码方式登录系统
  2. 服务端验证后,创建一个 Session 信息,并且将 SessionID 存到 cookie发送回浏览器
  3. 下次客户端再发起请求,自动带上 cookie 信息,服务端通过 cookie 获取 Session 信息进行校验

img

存在问题

  1. 只适用于Web场景,APP无法使用(APP 可没有地方存 cookie)

  2. Cookie不支持跨域,跨域无法携带。

    Chrome版本的cookie策略

Chrome版本cookie策略
>= Chrome80跨域无法携带;可设置实验功能选项来开启允许跨域携带
>= Chrome91跨域无法携带;删除了设置选项的开启方式,但可通过配置命令参数开启允许跨域携带
>= Chrome94跨域无法携带;没有开启办法
  1. 分布式系统存在Session同步问题。

    Session是存在单机上面的,每个节点服务保存有自己的Seesion,当用户在某一台服务器登录之后该服务器保存了Session,但是当再次请求负载均衡到另外一台服务器之后,该服务器没有在Session中保存该用户的信息。

  2. Cookie 存在 CSRF(跨站请求伪造)的风险。

    跨站请求伪造:是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。CSRF 利用的是网站对用户网页浏览器的信任。简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(比如购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户发起的操作。

    例如:

    黑客发现你经常访问的一个技术网站存在 CSRF 漏洞。发布文章支持 html 格式,进而在 html 中加入一些危险内容

     <img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
    

    假设 src 指向的地址是一个你平时用的购物网站的付款地址(当然只是举例,真正的攻击可没这么简单),如果你之前登录过并且标识你身份信息的 cookie 已经保存下来了。当你刷到黑客发布的这篇文章的时候,img 标签一加载,这个 CSRF 攻击就会起作用,在你不知情的情况下向这个网站付款了。

Cookie-Session的改进

  1. Cookie 不能在 APP 等非浏览器中使用,不用 cookie 做客户端存储,改用其他方式。Web 中可以使用 local storage,APP 中使用客户端数据库,这样既能这样就实现了跨域,并且避免了 CSRF 。
  2. Session 存到 Redis 等内存数据库中。提高了速度,又避免了 Session 同步问题。

改进后的认证流程:

  1. 用户输入用户名、密码或者用短信验证码方式登录系统
  2. 服务端经过验证,将认证信息构造好的数据结构存储到 Redis 中,并将 key 值返回给客户端;
  3. 客户端拿到返回的 key,存储到 local storage 或本地数据库
  4. 下次客户端再次请求,把 key 值附加到 header 或者 请求体中
  5. 服务端根据获取的 key,到 Redis 中获取认证信息
img

img

改进版Cookie-Session的缺点:

  • 改造起来费时费力
  • 还有可能存在漏洞

JWT方案

JWT 是一种Cookie-Session改造版的具体实现,省去自己造轮子的时间。

可以不用在服务端存储认证信息(比如 token),完全由客户端提供,服务端只要根据 JWT 自身提供的解密算法就可以验证用户合法性,而且这个过程是安全的。

JWT数据结构

JWT 最后的形式就是个字符串,它由头部载荷签名这三部分组成,中间以「.」分隔。

img

头部

头部以 JSON 格式表示,用于指明令牌类型和加密算法。

{
  "alg": "HS256",
  "typ": "JWT"
}

载荷

用来存储服务器需要的数据,比如用户信息,例如姓名、性别、年龄等,要注意的是重要的机密信息最好不要放到这里,比如密码等。

{
  "name": "古时的风筝",
  "introduce": "英俊潇洒"
}

另外,JWT 还规定了 7 个字段供开发者选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

这部分信息也是要用 Base64 编码的。

签名

签名公式:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),  Secret)

使用HMACSHA256算法计算得出,这个方法有两个参数,前一个参数是 (base64 编码的头部 + base64 编码的载荷)用点号相连,后一个参数是自定义的字符串密钥,密钥不要暴露在客户端,而应该服务器知道。

认证流程

1、在用户登录网站的时候,需要输入用户名、密码或者短信验证的方式登录,登录请求到达服务端的时候,服务端对账号、密码进行验证,然后计算出 JWT 字符串,返回给客户端。

2、客户端拿到这个 JWT 字符串后,存储到 cookie 或者 浏览器的 LocalStorage 中

3、再次发送请求,比如请求用户设置页面的时候,在 HTTP 请求头中加入 JWT 字符串或者直接放到请求主体中

4、服务端拿到这串 JWT 字符串后,使用 base64的头部和 base64 的载荷部分,通过HMACSHA256算法计算签名部分比较计算结果和传来的签名部分是否一致,如果一致,说明此次请求没有问题,如果不一致,说明请求过期或者是非法请求。

img

img

JWT怎么保证安全

  • 保证安全性的关键就是 HMACSHA256 或者与它同类型的加密算法,因为加密过程是不可逆的,所以不能根据传到前端的 JWT 传反解到密钥信息
  • **不同的头部和载荷加密之后得到的签名都是不同的。**改了载荷部分的信息,那最后加密出的结果肯定就和改之前的不一样的,所以,最后验证的结果就是不合法的请求。

别人拿到完整 JWT 还安全吗

1、假设载荷部分存储了权限级别相关的字段,强盗拿到 JWT 串后想要修改为更高权限的级别,这种情况下是肯定不会得逞的,因为加密出来的签名会不一样,服务器可能很容易的判别出来。

2、如果强盗拿到后不做更改,直接用呢,那就没有办法了,为了更大程度上防止被强盗盗取,应该使用 HTTPS 协议而不是 HTTP 协议,这样可以有效的防止一些中间劫持攻击行为。

3、有人就要说了,这一点也不安全啊,拿到 JWT 串就可以轻松模拟请求了。确实是这样,但是前提是你怎么样能拿到,除了上面说的中间劫持外,还有什么办法吗?

JWT缺陷

  • 一旦颁发一个 JWT 令牌,服务端就没办法废弃掉它,除非等到它自身过期。

  • 有很多应用默认只允许最新登录的一个客户端正常使用,不允许多端登录,JWT 就没办法做到,因为颁发了新令牌,但是老的令牌在过期前仍然可用。