[TOC]
# 简介
前后端分离通过 Restful API 进行数据交互时,如何验证用户的登录信息及权限。
由于 HTTP 协定是不储存状态的 (stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个 request 请求时它就把刚刚的资料忘了。于是我们的程序就不知道谁是谁,就要再验证一次。
所以为了保证系统安全,我们就需要验证用户否处于登录状态。
# 传统方式
前端登录,后端根据用户信息生成一个 token,并保存这个 token 和对应的用户 id 到数据库或 Session 中,接着把 token 传给用户,存入浏览器 cookie,之后**浏览器请求带上这个 cookie**,后端根据这个 cookie 值来查询用户,验证是否过期。
## 缺点
1. XSS 漏洞
如果我们的页面出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致用户 token 泄露,而作为后端识别用户的标识,cookie 的泄露意味着用户信息不再安全。
尽管我们通过转义输出内容,使用 CDN 等可以尽量避免 XSS 注入,但谁也不能保证在大型的项目中不会出现这个问题。
其实你**可以设置 httpOnly 以及 secure 项**。
1. 设置 httpOnly 后 cookie 将不能被 JS 读取,浏览器会自动的把它加在请求的 header 当中;
2. 设置 secure 的话,cookie 就只允许通过 HTTPS 传输。secure 选项可以过滤掉一些使用 HTTP 协议的 XSS 注入,但并不能完全阻止。
2. 服务器端的压力
如果将验证信息保存在数据库中,后端每次都需要根据 token 查出用户 id,这就增加了数据库的查询和存储开销。若把验证信息保存在 session 中,又加大了服务器端的存储压力。
## 疑问🤔️
httpOnly 选项使得 JS 不能读取到 cookie,那么 XSS 注入的问题也基本不用担心了。
但设置 httpOnly 就带来了另一个问题,就是很容易的被 XSRF,即跨站请求伪造。
当你浏览器开着这个页面的时候,另一个页面可以很容易的跨站请求这个页面的内容。因为 cookie 默认被发了出去。
那我们可不可以**不要服务器去查询呢**?
如果我们生成 token 遵循一定的规律,比如我们使用对称加密算法来加密用户 id 形成 token,那么服务端以后其实只要解密该 token 就可以知道用户的 id 是什么了。不过呢,我只是举个例子而已,要是真这么做,只要你的对称加密算法泄露了,其他人可以通过这种加密方式进行伪造 token,那么所有用户信息都不再安全了。恩,那用非对称加密算法来做呢,其实现在有个规范就是这样做的,接下来要介绍的 JWT。
# JWT 介绍
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。
它定义了一种简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。
JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。
它具备两个特点:
* 简洁(Compact)
可以通过 URL,POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快
* 自包含(Self-contained)
负载中包含了所有用户所需要的信息,避免了多次查询数据库
# JWT 组成
JWT 的三个部分依次如下:
* Header(头部)
* 进行 base64 编码
* Payload(负载)
* 进行 base64 编码
* 因为 `base64` 是对称解密的,意味着该部分信息可以归类为明文信息。
* Signature(签名)
* 将 Header 和 Payload 通过密钥 secret 和加盐算法进行加密后生成
写成一行,就是下面的样子:
```
Header.Payload.Signature
// 示例 Token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qjw1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U
```
如果你还想确认这些信息是否真的存在,可以拷贝 JWT 串到 http://jwt.io 的在线校验工具校验一下即可。
其实到这一步可能就有人会想了,HTTP 请求总会带上 token,这样这个 token 传来传去占用不必要的带宽啊。
其实相比 cookie ,token是可选择的携带在 header 头上;
还有可以去了解下 HTTP2,HTTP2 对头部进行了压缩,相信也解决了这个问题。
![](https://www.wangbase.com/blogimg/asset/201807/bg2018072303.jpg)
# JWT 使用
![](https://img.kancloud.cn/b1/60/b160a315a435dbdc02916b6d8622541c_919x620.png)
1. 首先,前端通过 Web 表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个 HTTP POST 请求。建议的方式是通过 SSL 加密的传输(https 协议),从而避免敏感信息被嗅探。
2. 后端核对用户名和密码成功后,将用户的 id 等其他信息作为 JWT Payload(负载),将其与头部分别进行 Base64 编码拼接后签名,形成一个 JWT。形成的 JWT 就是一个形同 `hhh.ppp.sss` 的字符串。
3. 后端将 JWT 字符串作为登录成功的返回结果返回给前端。**前端可以将返回的结果保存在 `localStorage` 或 `sessionStorage` 上,退出登录时前端删除保存的 JWT 即可**。
4. 前端在每次请求时将 JWT 放入 HTTP Header 中的 `Authorization` 位。(解决 XSS 和 XSRF 问题)
```
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token // JWT 规定的的表示形式
}
})
```
5. 后端检查是否存在,如存在验证 JWT 的有效性。例如,检查签名是否正确;检查 Token 是否过期;检查 Token 的接收方是否是自己(可选)。
服务器将用户信息和自己的密钥通过既定好的算法进行签名,然后将发来的签名和生成的签名比较,严格相等则说明用户信息没被篡改和伪造,验证通过。
6. 验证通过后后端使用 JWT 中包含的用户信息进行其他逻辑操作,返回相应结果。
## 和 Session 方式存储 id 的差异
Session 方式存储用户 id 的最大弊病在于 Session 是存储在服务器端的,所以需要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些 KV 数据库和一系列缓存机制来实现 Session 的存储。
1. JWT 方式将用户状态分散到了客户端中,可以明显减轻服务端的 内存压力。
2. JWT 方式让服务器有一些计算压力(例如加密、编码和解码),但是这些压力相比磁盘存储而言可能就不算什么了。
除了用户 id 之外,还可以存储其他的和用户相关的信息,例如该用户是否是管理员、用户所在的分组等。
具体是否采用,需要在不同场景下用数据说话。
## 单点登录(SSO)
Session 方式来存储用户 id,一开始用户的 Session 只会存储在一台服务器上。
对于有多个子域名的站点,每个子域名至少会对应一台不同的服务器,例如:`www.taobao.com`,`nv.taobao.com`,`nz.taobao.com`,`login.taobao.com`。
所以如果要实现在`login.taobao.com`登录后,在其他的子域名下依然可以取到 Session,这要求我们在多台服务器上同步 Session。使用 JWT 的方式则不会存在这个问题,因为用户的状态已经被传送到了客户端。
# 总结
## 优点
因为 json 的通用性,所以 JWT 是可以进行跨语言支持的,像 JAVA、JavaScript、NodeJS、PHP 等很多语言都可以使用。
因为有了 payload 部分,所以 JWT 可以在自身存储一些其他业务逻辑所必须的非敏感信息。
便于传输,jwt 的构成非常简单,字节占用很小,所以它是非常便于传输的。
它不需要在服务端保存会话信息,所以它易于应用的扩展。
## 安全相关
1. 不应该在 jwt 的 payload 部分存放敏感信息,因为该部分是客户端可解密的部分。
2. 保护好 secret 私钥,该私钥非常重要。
3. 如果可以,请使用 https 协议。
# 其他资料
[基于 JWT 的 Token 认证的安全问题](https://www.cnblogs.com/ypppt/p/13332007.html)
[基于 Token 的身份验证](https://ninghao.net/blog/2834)
[Vue刷新token,判断token是否过期、失效的最简便的方法](https://blog.csdn.net/weixin_40667613/article/details/90639614)
# 参考
[通过一个案例理解 JWT](https://juejin.im/post/5ba37c50e51d450e664b3fc3)
[前后端分离之 JWT 用户认证](https://lion1ou.win/2017/01/18/)
[前后端分离, 前端如何防止直接输入 URL 进入页面?](https://www.zhihu.com/question/269101275)
[JSON Web Token 入门教程](https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html)
[前后端分离模式下,如何跟踪用户状态?](https://blog.csdn.net/hwhsong/article/details/82020526)
- 讲解 Markdown
- 示例
- SVN
- Git笔记
- github 相关
- DESIGNER'S GUIDE TO DPI
- JS 模块化
- CommonJS、AMD、CMD、UMD、ES6
- AMD
- RequrieJS
- r.js
- 模块化打包
- 学习Chrome DevTools
- chrome://inspect
- Chrome DevTools 之 Elements
- Chrome DevTools 之 Console
- Chrome DevTools 之 Sources
- Chrome DevTools 之 Network
- Chrome DevTools 之 Memory
- Chrome DevTools 之 Performance
- Chrome DevTools 之 Resources
- Chrome DevTools 之 Security
- Chrome DevTools 之 Audits
- 技巧
- Node.js
- 基础知识
- package.json 详解
- corepack
- npm
- yarn
- pnpm
- yalc
- 库处理
- Babel
- 相关库
- 转译基础
- 插件
- AST
- Rollup
- 基础
- 插件
- Webpack
- 详解配置
- 实现 loader
- webpack 进阶
- plugin 用法
- 辅助工具
- 解答疑惑
- 开发工具集合
- 花样百出的打包工具
- 纷杂的构建系统
- monorepo
- 前端工作流
- 爬虫
- 测试篇
- 综合
- Jest
- playwright
- Puppeteer
- cypress
- webdriverIO
- TestCafe
- 其他
- 工程开发
- gulp篇
- Building With Gulp
- Sass篇
- PostCSS篇
- combo服务
- 编码规范检查
- 前端优化
- 优化策略
- 高性能HTML5
- 浏览器端性能
- 前后端分离篇
- 分离部署
- API 文档框架
- 项目开发环境
- 基于 JWT 的 Token 认证
- 扯皮时间
- 持续集成及后续服务
- 静态服务器搭建
- mock与调试
- browserslist
- Project Starter
- Docker
- 文档网站生成
- ddd