相信大家看了上图会发现存在两个问题 1、 token 只存储在浏览器中,服务端却没有存储,这样的话我随便搞个 token 传给 server 也行? 答:server 会有一套校验机制,校验这个 token 是否合法。 2、怎么不像 session 那样根据 sessionId 找到 userid 呢,这样的话怎么知道是哪个用户? 答:token 本身携带 uid 信息 第一个问题,如何校验 token 呢?我们可以借鉴 HTTPS 的签名机制来校验。先来看 jwt token 的组成部分 可以看到 token 主要由三部分组成 当 server 收到浏览器传过来的 token 时,它会首先取出 token 中的 header + payload,根据密钥生成签名,然后再与 token 中的签名比对,如果成功则说明签名是合法的,即 token 是合法的。而且你会发现 payload 中存有我们的 userId,所以拿到 token 后直接在 payload 中就可获取 userid,避免了像 session 那样要从 redis 去取的开销 画外音:header, payload 实际上是以 base64 的形式存在的,文中为了描述方便,省去了这一步。 你会发现这种方式确实很妙,只要 server 保证密钥不泄露,那么生成的 token 就是安全的,因为如果伪造 token 的话在签名验证环节是无法通过的,就此即可判定 token 非法。 可以看到通过这种方式有效地避免了 token 必须保存在 server 的弊端,实现了分布式存储,不过需要注意的是,token 一旦由 server 生成,它就是有效的,直到过期,无法让 token 失效,除非在 server 为 token 设立一个黑名单,在校验 token 前先过一遍此黑名单,如果在黑名单里则此 token 失效,但一旦这样做的话,那就意味着黑名单就必须保存在 server,这又回到了 session 的模式,那直接用 session 不香吗。所以一般的做法是当客户端登出要让 token 失效时,直接在本地移除 token 即可,下次登录重新生成 token 就好。 另外需要注意的是 token 一般是放在 header 的 Authorization 自定义头里,不是放在 Cookie 里的,这主要是为了解决跨域不能共享 Cookie 的问题 (下文详述) Cookie 与 Token 的简单总结Cookie 有哪些局限性?
1、 Cookie 跨站是不能共享的,这样的话如果你要实现多应用(多系统)的单点登录(SSO),使用 Cookie 来做需要的话就很困难了(要用比较复杂的 trick 来实现,有兴趣的话可以看文末参考链接) 画外音: 所谓单点登录,是指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 但如果用 token 来实现 SSO 会非常简单,如下 只要在 header 中的 authorize 字段(或其他自定义)加上 token 即可完成所有跨域站点的认证。 2、 在移动端原生请求是没有 cookie 之说的,而 sessionid 依赖于 cookie,sessionid 就不能用 cookie 来传了,如果用 token 的话,由于它是随着 header 的 authoriize 传过来的,也就不存在此问题,换句话说token 天生支持移动平台,可扩展性好 综上所述,token 具有存储实现简单,扩展性好这些特点。 token 有哪些缺点
那有人就问了,既然 token 这么好,那为什么各个大公司几乎都采用共享 session 的方式呢,可能很多人是第一次听到 token,token 不香吗? token 有以下两点劣势: 1、 token 太长了 token 是 header, payload 编码后的样式,所以一般要比 sessionId 长很多,很有可能超出 cookie 的大小限制(cookie 一般有大小限制的,如 4kb),如果你在 token 中存储的信息越长,那么 token 本身也会越长,这样的话由于你每次请求都会带上 token,对请求来是个不小的负担 2、 不太安全 网上很多文章说 token 更安全,其实不然,细心的你可能发现了,我们说 token 是存在浏览器的,再细问,存在浏览器的哪里?既然它太长放在 cookie 里可能导致 cookie 超限,那就只好放在 local storage 里,这样会造成安全隐患,因为 local storage 这类的本地存储是可以被 JS 直接读取的,另外由上文也提到,token 一旦生成无法让其失效,必须等到其过期才行,这样的话如果服务端检测到了一个安全威胁,也无法使相关的 token 失效。 所以 token 更适合一次性的命令认证,设置一个比较短的有效期 误解: Cookie 相比 token 更不安全,比如 CSRF 攻击首先我们需要解释下 CSRF 攻击是怎么回事 攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过(cookie 里带来 sessionId 等身份认证的信息),所以被访问的网站会认为是真正的用户操作而去运行。 比如用户登录了某银行网站(假设为 http://www.examplebank.com/,并且转账地址为 http://www.examplebank.com/withdraw?amount=1000&transferTo=PayeeName),登录后 cookie 里会包含登录用户的 sessionid,攻击者可以在另一个网站上放置如下代码 那么如果正常的用户误点了上面这张图片,由于相同域名的请求会自动带上 cookie,而 cookie 里带有正常登录用户的 sessionid,类似上面这样的转账操作在 server 就会成功,会造成极大的安全风险 CSRF 攻击的根本原因在于对于同样域名的每个请求来说,它的 cookie 都会被自动带上,这个是浏览器的机制决定的,所以很多人据此认定 cookie 不安全。 使用 token 确实避免了CSRF 的问题,但正如上文所述,由于 token 保存在 local storage,它会被 JS 读取,从存储角度来看也不安全(实际上防护 CSRF 攻击的正确方式是用 CSRF token) 所以不管是 cookie 还是 token,从存储角度来看其实都不安全,都有暴露的风险,我们所说的安全更多的是强调传输中的安全,可以用 HTTPS 协议来传输, 这样的话请求头都能被加密,也就保证了传输中的安全。 其实我们把 cookie 和 token 比较本身就不合理,一个是存储方式,一个是验证方式,正确的比较应该是 session vs token。 总结session 和 token 本质上是没有区别的,都是对用户身份的认证机制,只是他们实现的校验机制不一样而已(一个保存在 server,通过在 redis 等中间件获取来校验,一个保存在 client,通过签名校验的方式来校验),多数场景上使用 session 会更合理,但如果在单点登录,一次性命令认证上使用 token 会更合适,最好在不同的业务场景中合理选型,才能达到事半功倍的效果。 巨人的肩膀 |