JWT 理论介绍
参考文档:JSON Web Token 入门教程 - 阮一峰的网络日志 (ruanyifeng.com)
什么是JWT?
- JWT:即 JSON Web Tokens
- 是一种跨域认证解决方案
JWT 解决了什么问题?
- 数据传输简单高效
- jwt 会生成签名,保证传输安全性
- jwt 具有时效性
- jwt 更高效利用集群做好单点登录
JWT 原理
- 服务器认证后,生成一个JSON对象,后续通过JSON进行通信
JWT 数据结构
- Header(头部)
- Payload(负载)
- Signature(签名)
Header结构
{
"alg": "HS256",
"typ": "JWT"
}
Payload结构
签名
JWT 使用方式
- /api?token=xxx
- cookie 写入 token
- storage 写入 token,请求头添加:
Authorization:Bearer <token>
通过 JWT 生成Token
文档地址:jsonwebtoken - npm (npmjs.com)
在后端项目安装
npm install jsonwebtoken
在/router/users.js中引入
const jwt = require('jsonwebtoken')
使用,生成token,并随接口返回
// imooc 是密钥
let data = 用户信息对象;
let token = jwt.sign({
data,
}, 'imooc', { expiresIn: 30 })
请求携带Token
我们在登录的时候,将用户信息存储到了 localStorage中了,其中包含用户的token信息
所以在请求拦截的时候,在headers.Authorization中携带上token
这里应该还需要添加判断是否携带token,因为有的接口是不需要token的,目前我们先每次请求都携带上token
// 请求拦截
service.interceptors.request.use((config) => {
const headers = config.headers;
let token = storage.getItem('userInfo') ? storage.getItem('userInfo').token : ''
if (!headers.Authorization) {
headers.Authorization = 'Bearer ' + token
}
return config;
});
接口验证Token
const router = require('koa-router')()
const jwt = require('jsonwebtoken');
router.get('/count', (ctx)=>{
try {
// 获取请求中携带的Token
let authorization = ctx.request.header.authorization;
let token = authorization.split('Bearer ')[1];
// 验证Token,验证通过继续执行,验证不通过则走 catch
let payload = jwt.verify(token, 'imooc');
ctx.body = 'token验证通过'
} catch(e) {
ctx.body = 'token验证未通过'
}
})
关于返回值和密钥的说明:
/**
* 因为生成token的时候,传的是是用户信息,密钥使用的是imooc
*/
let data = 用户信息对象;
let token = jwt.sign({
data,
}, 'imooc', { expiresIn: 30 })
/**
* 所以验证token的时候,返回值就是我们前边生成的时候传的用户信息对象,使用的密钥也是imocc
*/
let payload = jwt.verify(token, 'imooc');
// payload 打印出来如下:
> data: {deptId: [], state: 1, role: 1, roleList: [], createTime: "2021-10-09T07:47:36.566Z",…}
> exp: 1633765828
> iat: 1633765798
Token拦截
这里我们需要引入一个中间件的概念,在app.js中有好多中间件的使用;
如下
/**
* ctx 上下文对象,同接口中的ctx
* next() 继续执行
*/
app.use(async (ctx, next) => {
await next()
})
koa-jwt 中间件
文档地址:koa-jwt - npm (npmjs.com)
安装
npm i koa-jwt -S
使用
const Koa = require('koa')
const app = new Koa()
const koaJwt = require('koa-jwt')
const utils = require('./utils/utils')
app.use(async (ctx, next) => {
return next().catch((err)=>{
if(err.status == 401) {
ctx.status = 200;
ctx.body = utils.fail('Token 认证失败', utils.CODE.AUTH_ERROR)
} else {
throw err;
}
})
})
// 要放在后边,secret的值是密钥
app.use(koaJwt({secret: 'imooc'}))
判断是否验证Token
因为有些接口是不需要验证token的,比如说登录接口,这时候我们就使用koa-jwt的unless方法,规定不需要验证token的接口;
// 比如不需要验证 /users/login
app.use(koaJwt({secret: 'imooc'}).unless({
path: [/^/users/login/]
}))
// 需要注意的是,前端请求的是 /api/users/login,因为要配置跨域,所以加上了/api
// 但是后端接收到的请求是没有 /api,所以要注意下不要把/api也写上
从数据库中,查询指定的字段
因为我们不会将数据库中查询出来的所有字段,都返回给客户端
比如,查询用户信息的时候,一些像密码这种比较敏感的信息是不会返回的
这里就用到了一个mongo的语法
// 查询条件
let query = {
userName,
userPassword
}
// 需要查询出来的字段,是个字符串-多个字段用空格分开
let keys = 'userId userName userEmail'
// User.findOne(查询条件, 字段)
let res = await User.findOne(query, keys)
findOne() 返回规定的字段,有三种写法
- 第一种就是上边的字符串形式,多个字段之间使用空格了连接
let keys = 'userId userName userEmail'
let res = await User.findOne(query, keys)
- 传入一个对象,1返回,0不返回;(0和1,只能写单独的一个,不能有的字段是0,有的是1,会报错)
let keys = {
userName: 1,
userId: 1
}
let res = await User.findOne(query, keys)
- 通过
select()方法指定,值和方式一相同
let keys = 'userId userName userEmail'
let res = await User.findOne(query).select(keys)