小小繼續學習,這次學習的內容是egg-jwt 相關。
創建egg項目
這裡創建一個egg新項目,這裡使用的是ts模式。
npm init egg --type=tsnpm install安裝相關的包
這裡創建並安裝完成以後,需要再次初始化倆包,分別為egg-cors與egg-jwt token 生成的驗證包
npm install egg-cors egg-jwt --save配置相關插件
這裡配置相關的插件
import { EggPlugin } from 'egg';const plugin: EggPlugin = { jwt: { enable: true, package: "egg-jwt" }, cors: { enable: true, package: 'egg-cors', }};export default plugin;配置默認配置文件
config.jwt = { secret: "123456"//自定義 token 的加密條件字符串};config.security = { csrf: { enable: false, ignoreJSON: true }, domainWhiteList: ['http://localhost:8080'],//允許訪問接口的白名單};config.cors = { origin:'*', allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'};這裡配置完成了相關的默認配置
在根目錄聲明any類型
這裡需要在跟目錄聲明一個any類型,用於前後端發送相關的字符串參數。
import 'egg';declare module 'egg' { interface Application { jwt: any; }}配置相關路由
這裡在app/router.ts 創建相關的路由
import { Application } from 'egg';export default (app: Application) => { const { controller, router, jwt } = app; //正常路由 router.post('/admin/login', controller.admin.login); /* * 這裡的第二個對象不再是控制器,而是 jwt 驗證對象,第三個地方才是控制器 * 只有在需要驗證 token 的路由才需要第二個 是 jwt 否則第二個對象為控制器 **/ router.post('/admin',jwt, controller.admin.index);};這裡就配置完成了相關的路由。
編寫路由對應的控制器
這裡編寫路由所對應的控制器這個控制器在app/controller/home.ts 目錄下
import { Controller } from 'egg';export default class AdminController extends Controller { // 驗證登錄並且生成 token public async login() { const { ctx ,app} = this; //獲取用戶端傳遞過來的參數 const data = ctx.request.body; // 進行驗證 data 數據 登錄是否成功 // ......... //成功過後進行一下操作 //生成 token 的方式 const token = app.jwt.sign({ username: data.username, //需要存儲的 token 數據 //...... }, app.config.jwt.secret); // 生成的token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1NjAzNDY5MDN9.B95GqH-fdRpyZIE5g_T0l8RgzNyWOyXepkLiynWqrJg // 返回 token 到前端 ctx.body = token; }; //訪問admin數據時進行驗證token,並且解析 token 的數據 public async index() { const { ctx ,app} = this; console.log(ctx.state.user); /* * 列印內容為:{ username : 'admin', iat: 1560346903 } * iat 為過期時間,可以單獨寫中間件驗證,這裡不做細究 * 除了 iat 之後,其餘的為當時存儲的數據 **/ ctx.body = {code:0,msg:'驗證成功'}; }}前端請求相匹配
這裡只需要在前端的authorization欄位裡,添加相關的配置信息即可。
axios({ method: 'post', url: 'http://127.0.0.1:7001/admin', data: { username: 'admin', lastName: '123456' }, headers:{ // 切記 token 不要直接發送,要在前面加上 Bearer 字符串和一個空格 'Authorization':`Bearer ${token}` }}).then(res=>{ console.log(res.data)})這裡就完成了egg.js 結合jwt完成相關的驗證
小tips
這裡插曲一個小tips,這裡使用的是jsonwebtoken。這裡使用jsonwebtoken實現token相關認證機制。
安裝
這裡安裝相關的依賴
npm install jsonwebtoken編寫中間件
在middleware文件下新建一個jwt.ts 文件
'use strict'const fs = require('fs')const path = require('path')const jwt = require('jsonwebtoken') //引入jsonwebtokenmodule.exports = (options, app) => { return async function userInterceptor(ctx, next) { let authToken = ctx.header.authorization // 獲取header裡的authorization if (authToken) { authToken = authToken.substring(7) const res = verifyToken(authToken) // 解密獲取的Token if (res.corpid && res.userid) { // 如果需要限制單端登陸或者使用過程中廢止某個token,或者更改token的權限。也就是說,一旦 JWT 籤發了,在到期之前就會始終有效 // 此處使用redis進行保存 const redis_token = await app.redis.get('loginToken').get(res.corpid + res.userid) // 獲取保存的token if (authToken === redis_token) { ctx.locals.corpid = res.corpid ctx.locals.userid = res.userid await next() } else { ctx.body = { code: 50012, msg: '您的帳號已在其他地方登錄' } } } else { ctx.body = { code: 50012, msg: '登錄狀態已過期' } } } else { ctx.body = { code: 50008, msg: '請登陸後再進行操作' } } }}// 解密,驗證function verifyToken(token) { const cert = fs.readFileSync(path.join(__dirname, '../public/rsa_public_key.pem')) // 公鑰,看後面生成方法 let res = '' try { const result = jwt.verify(token, cert, { algorithms: [ 'RS256' ] }) || {} const { exp } = result, current = Math.floor(Date.now() / 1000) if (current <= exp) res = result.data || {} } catch (e) { console.log(e) } return res}使用中間件
這裡在config.default.js中加入如下的配置,實現中間件的開啟和配置
// 方法一:在應用中使用中間件config.middleware = [ 'jwt' ]config.jwt = { enable: true, ignore: [ '/api/v1/test/', '/public/' ], // 哪些請求不需要認證}// 方法二:router中使用中間件module.exports = app => { const jwt = app.middleware.jwt(); app.router.get('/api/v1/test/', jwt, app.controller.test.test);};token生成
這裡卸載文件裡,用於調用,生成相關的token
loginToken(data, expires = 7200) { const exp = Math.floor(Date.now() / 1000) + expires const cert = fs.readFileSync(path.join(__dirname, '../public/rsa_private_key.pem')) // 私鑰,看後面生成方法 const token = jwt.sign({ data, exp }, cert, { algorithm: 'RS256' }) return token}調用相關的生成方法
const token = ctx.helper.loginToken({ corpid: usersData.corpid, userid: usersData.userid }, 7200) // token生成await app.redis.get('loginToken').set(usersData.corpid + usersData.userid, token, 'ex', 7200) // 保存到redisctx.body = { data: { token, expires: this.config.login_token_time }, code: 1, msg: '登錄成功' } // 返回前端前端使用
這裡前端使用headers,在後面加上相關的空格。
例:axios中// request攔截器service.interceptors.request.use(config => { if (store.getters.token) { config.headers['Authorization'] = `Bearer ${getToken()}` } return config}, error => { console.log(error) Promise.reject(error)})這裡就完成了相關jwt的生成與使用。
小明菜市場