HTTP 是一种无状态的协议,也就是 HTTP 无法保存客户端的信息,没办法区分每次请求的不一样。
设想这样的场景,A 和 B 同时修改我的的文章,服务器同时收到两个 post 请求,但浏览器并不知道哪一个请求是 A 哪一个请求是 B,须要一个标识符(token)来标记一串信息而且在请求的时候带上前端
Token 是服务器生成的一串字符,做为客户端请求的令牌。当第一次登录后,服务器会分发 Tonken 字符串给客户端。后续的请求,客户端只需带上这个 Token,服务器便可知道是该用户的访问。
我的理解就是一串被服务器加密过的我的信息,好比下面这个:vue
acess_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZWRhMTBjOTI0NThmNDAwMmFjZDEyMTAiLCJuaWNrX25hbWUiOiLlsI_osaoxOTk2IiwiY3JlYXRlZF90aW1lIjoiMjAyMC0wNi0wNVQwOTozMDo0OS42MThaIiwidXBkYXRlZF90aW1lIjoiMjAyMC0wNi0wNVQwOToyOToyMC4wNzlaIiwiaWF0IjoxNTkxMzQ5NDY4LCJleHAiOjE1OTE5NTQyNjh9.GmUJRXHed7M1xJyPaFFgaQKJoS-w8-l3N_PQFPiwwTE
服务器经过秘钥解密从而得到当前请求者的信息node
blog 已经基本完成,而且用上了 ssr 渲染ios
本文主要是讲 token 验证,其余再也不累述git
个人 blog 对 get 类型的请求不作 token 验证,其余会修改资源的请求如 POST、PUT 会作 token 验证github
可拆解为下面 3 个步骤web
1.前端(vue.js)redis
使用 axios 库,而且在 request 拦截器中把 token 塞到请求头 header ,在 response 拦截器中统一对错误状态码进行全局提示数据库
登陆成功以后把 token 写入 浏览器缓存 中,每次请求都带上json
import Vue from 'vue'; import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; axios.interceptors.request.use( async (config: AxiosRequestConfig) => { const acess_token = await Vue.prototype.$getCacheData('acess_token'); # 缓存中读取token if (acess_token) { config.headers.acess_token = acess_token; } return config; }, (err: any) => Promise.reject(err), ); axios.interceptors.response.use( (response: AxiosResponse) => { if (response.data.ret === 200) { return response; } else { Vue.prototype.$global_fail(response.data.content); return Promise.reject(response); } }, (err: any) => { console.log(err); if (err.code === 'ECONNABORTED' && err.message.indexOf('timeout') !== -1) { Vue.prototype.$global_error('请求超时,请联系管理员'); } if (err.response) { Vue.prototype.$global_error(decodeURI(err.response.data.msg || err.response.data.message)); } return Promise.reject(err); }, );
关于前端缓存,这里我推荐一个 localForage 库,很实用
localForage 是一个 JavaScript 库,能存储多种类型的数据,而不单单是字符串。localForage 有一个优雅降级策略,若浏览器不支持 IndexedDB 或 WebSQL,则使用 localStorage。
但注意,它的操做都是异步的,能够本身封装一层把它改为同步的
import Vue from 'vue'; import localForage from 'localforage'; Vue.prototype.$setCacheData = async (key: string, data: any): Promise<void> => await localForage.setItem(key, data); Vue.prototype.$getCacheData = async (key: string): Promise<string | null> => await localForage.getItem(key) || null; Vue.prototype.$clearCache = () => localForage.clear();
2.后端(egg.js)
用户登陆,使用 jsonwebtoken 生成 token
jsonwebtoken 的详情请点击: node-jsonwebtoken
加密解密使用也比较简单,直接给出 UserService 方法,其中 secret 为秘钥,且能够设置 token 过时时间
# /app/service/user.ts import * as jwt from 'jsonwebtoken'; export default class UserService extends Service { private secret = 'Hello__World'; # 秘钥 async createToken(user: User): Promise<string> { const payload = { _id: user._id, nick_name: user.nick_name, created_time: user.created_time, updated_time: user.updated_time, }; return jwt.sign(payload, this.secret, { expiresIn: '7d' }); # 过时时间 } checkToken(token: string): User { try { # 根据秘钥解密token return jwt.verify(token, this.secret); } catch (e) { throw '无效的token'; } } }
在中间件中进行 token 验证,若失败直接返回
开启 verify 中间件,并只对特定的 POST 请求进行验证:
开启中间件
# /config/config.default.ts config.middleware = ['verify']; config.verify = { enable: true, # 只对POST请求作验证 match(ctx) { return ctx.request.method === 'POST'; }, };
在 verify 调用 checkToken 方法验证 token
# /app/middleware/verify.ts module.exports = () => { return async (ctx, next) => { if (ctx.path.startsWith('/api/user/login') || ctx.path.startsWith('/api/user/sendCode') || ctx.path.startsWith('/api/user/register')) { return await next(); } try { const acess_token: string = ctx.request.header.acess_token; if (!acess_token) { throw '请登陆'; } else { await ctx.service.user.checkToken(acess_token); # 验证token return await next(); } } catch (e) { # token验证失败会走到这里,返回自定义状态码 console.log(e); ctx.body = { ret: 304, content: `${e}`, }; } }; };
至此 token 验证就完了,若有不足,欢迎指出
END