内容概括
主要产出
- 了解
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
标准。