内容概括
主要产出
-
了解
Web
常用的登录鉴权方式
主要内容
-
cookie
和session
-
JWT
-
SSO
和OAuth2
关于短信验证码
-
用户体验好,无需注册,无需记住密码
-
验证码需要收费,所以需要防止恶意共计,恶意刷接口
介绍 Session 登录
cookie 做登录校验的过程
-
前端输入用户名密码,传给后端
-
后端验证成功,返回信息时
set-cookie
-
接下来的所有接口访问,都自动带上
cookie
为何会有 Session
-
cookie
只存储userId
,不暴露用户信息 -
用户信息存储在
session
中
Session 的优点
-
原理简单,易于学习
-
用户信息存储在服务端,可以快速封禁某个登录的用户
Session 的缺点
-
占用服务端内存,有硬件成本
-
多进程,多服务器时,不好同步,一般使用第三方
redis
存储,成本高 -
跨域传递
cookie
,需要特殊配置
介绍 JWT 登录
JWT
的全称是 JSON Web Token
JWT 的过程
-
前端输入用户名密码,传递给后端
-
后端验证成功,返回一段
token
字符串,将用户信息加密之后得到的结果 -
前端获取
token
之后,存储下来 -
以后访问接口,都在
header
中携带这段token
JWT 的优点
-
不占用服务器内存
-
多进程,多服务器,不受影响
-
不受跨域限制
JWT 的缺点
-
无法快速封禁登录的用户
JWT 和 Session 的重要区别
-
JWT
用户信息存储在客户端 -
Session
用户信息存储在服务器端
为何选择 JWT
-
没有快速封禁登录用户的需求
-
JWT
成本低,维护简单 -
需要考虑跨域的扩展性
代码演示
-
安装
npm
插件,koa-jwt
、jsonwebjson
-
封装
jwt
中间件,并使用 -
封装
loginCheck
中间件 -
相关的配置项,构造函数等
安装插件
npm i koa-jwt jsonwebtoken -S
封装 jwt 中间件
增加配置
// src\config\constant.js module.exports = { // jwt 秘钥 JWT_SECRET: 'warbler_for-json#web$token', // jwt 忽略默认验证的 path:全部忽略即可,需要登录验证的,用自己封装的 loginCheck JWT_IGNORE_PATH: [/\//], }
封装中间件
// src\middlewares\jwt.js const jwtKoa = require('koa-jwt') const { JWT_SECRET, JWT_IGNORE_PATH } = require('../config/constant') const jwt = jwtKoa({ secret: JWT_SECRET, // jwt 秘钥 cookie: 'jwt_token', // 使用 cookie 存储 token }).unless({ // 定义哪些路由忽略 jwt 验证 path: JWT_IGNORE_PATH, }) module.exports = jwt
使用
// src\app.js const jwt = require('./middlewares/jwt') // 配置 jwt 中间件 app.use(jwt)
封装 JWT 工具
// src\utils\jwt.js const util = require('util') const jwt = require('jsonwebtoken') // jwt 密钥 const { JWT_SECRET } = require('../config/constant') // jwt 过期时间 const { jwtExpiresIn } = require('../config/index') const verify = util.promisify(jwt.verify) /** * jwt verify 解密 * @param {string} token token */ async function jwtVerify(token) { const data = await verify(token.split(' ')[1], JWT_SECRET) // 去掉前面的 Bearer return data } /** * jwt sign 加密 * @param {Object} data data */ function jwtSign(data) { const token = jwt.sign(data, JWT_SECRET, { expiresIn: jwtExpiresIn }) return token } // 导出解密,加密两个方法 module.exports = { jwtVerify, jwtSign, }
封装 loginCheck 中间件
封装数据模型
// src\res-model\index.js /** * 基础模型,包括 errno data 和 message */ class BaseRes { constructor({ errno, data, message }) { this.errno = errno if (data) { this.data = data } if (message) { this.message = message } } } /** * 执行失败的数据模型 */ class ErrorRes extends BaseRes { constructor({ errno = -1, message = '', data }, addMessage = '') { super({ errno, message: addMessage ? `${message} - ${addMessage}` // 有追加信息 : message, data, }) } } /** * 执行成功的数据模型 */ class SuccessRes extends BaseRes { constructor(data = {}) { super({ errno: 0, data, }) } } module.exports = { ErrorRes, SuccessRes, }
封装错误信息集合
// src\res-model\failInfo\error.js module.exports = { // 统一错误处理 serverErrorFailInfo: { errno: -1, message: '运行错误', }, // 404 notFoundFailInfo: { errno: -2, message: '404 Not Found', }, }
封装中间件
// src\middlewares\loginCheck.js // 解密 const { jwtVerify } = require('../utils/jwt') // 执行失败的数据模型 const { ErrorRes } = require('../res-model/index') // 错误信息集合 const { loginCheckFailInfo } = require('../res-model/failInfo/index') /** * 登录校验 * @param {Object} ctx ctx * @param {function} next next */ module.exports = async function loginCheck(ctx, next) { // 失败信息 const errRes = new ErrorRes(loginCheckFailInfo) // 获取 token const token = ctx.header.authorization if (!token) { ctx.body = errRes return } let flag = true try { const userInfo = await jwtVerify(token) delete userInfo.password // 屏蔽密码 // 验证成功,获取 userInfo ctx.userInfo = userInfo } catch (ex) { flag = false ctx.body = errRes } if (flag) { // 继续下一步 await next() } }
介绍 SSO 和 OAuth2
-
SSO
单点登录 -
OAuth2
第三方鉴权
使用 cookie 实现单点登录
简单的,如果业务系统都在同一主域名下,比如 wenku.baidu.com
、 tieba.baidu.com
,就可以直接把 cookie domain
设置为主域名 baidu.com
,百度也就是这么干的
SSO
OAuth2
SSO
是 OAuth2
的实际案例,其他常见的还有微信登录,github
登录等,当涉及到第三方用户登录校验时,都会使用 OAuth2
标准。