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