目录
设置找回密码请求拦截器
1.相关参数
2.约定
代码实现
1. 实现思路
2. 实现代码
校园统一身份认证系统:
基于网络安全,找回密码、重新设置密码的流程和正常登录流程中密钥等请求头不一致。
设置找回密码请求拦截器
1.相关参数
- clientId 应用ID,在点击忘记密码跳转后携带,如下所示:
http://192.168.31.134:8080/? clientId = webApp &secretKey = 3dtii4jr91iz8ypj &secretValue = ecmo5dro13as5y79
- secretKey 密钥的key,在点击忘记密码跳转后携带,如下所示:
http://192.168.31.134:8080/ ?clientId = webApp &secretKey = 3dtii4jr91iz8ypj &secretValue = ecmo5dro13as5y79
- secretValue 密钥的value,在点击忘记密码跳转后携带,如下所示:
http://192.168.31.134:8080/ ?clientId = webApp &secretKey = 3dtii4jr91iz8ypj &secretValue = ecmo5dro13as5y79
- nonce 随机字符串,10分钟内不要重复
- timestamp 时间戳(毫秒),纯数字
- sign 签名,利用上面的参数,使用SM3加密算法生成的加密字符串,加密前的明文如下所示(加密时没有用到secretKey):
nonce = "{ 随机字符串 }" ×tamp = { 时间戳 ( 毫秒 )} &clientId = "{ 应用 ID}" &secret = "{secretValue}"
- jwtSecret 生成jwt的密钥,值是 8w9L95DwBCD9xjkR
2.约定
从统一登录页点击忘记密码跳转时,会携带 1 个参数,如下所示:
http://192.168.31.134:8080/AccountBack ?key = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRJZCI6IndlYkFwcCIsInNlY3JldEtleSI6ImF2dGxuYXlldnBtbWo5NDgiLCJzZWNyZXRWYWx1ZSI6InMzZ2Jma2QzdDI2YWJ5NzQifQ.y1eTagvFM8gPxC1k2oqZIwr_9-IhyHAhWJqkZ8CFrnI
将 clientId 、 secretKey 、 secretValue 、 timeEnd 作为参数,以 jwtSecret 为密钥,生成的结果
就是 key ;其中 timeEnd 是 key 有效截止时间的时间戳。
每次请求 前都需要判断 key 是否还有效,如果无效需要重新开始忘记密码的流程。
每次请求 都需要以 clientId , timestamp , nonce , secretKey , sign 这 5 个值为参数,以 jwtSecret
为密钥生成 key ;并将结果放入请求头的 key 参数中。
【 注 】 :
1. 生成 key 时需要的是 secretKey ,而不是 secretValue。
代码实现
1. 实现思路
- jwtDecode对key解码。
- 获取数据:secretKey密钥,secretValue密钥值,timeEnd有效截至时间,redirectUri退回登录时需要携带url的标识。
- 获取当前时间戳检查是否超过有效截至时间。
- 生成签名sign、随机字符串nonce(无长度字符限制,为避免出错现在在数字和字母中,符号在解析过程中容易出错),jwt加密重新生成新的key。
2. 实现代码
使用router 文件 index.js 中的 accessToken:
const router = new VueRouter({mode: "history",base: process.env.BASE_URL,routes,
});
const VueRouterPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(to) {return VueRouterPush.call(this, to).catch((err) => err);
};router.beforeEach((to, from, next) => {const fullPath=to.fullPath.match(/\?(.*)/)if(fullPath) {const searchParams = new URLSearchParams(fullPath[1])if (searchParams.get('key')) {localStorage.setItem('accessToken', searchParams.get('key')) next(to.path)}
}else {next()
}})
export default router;
request1.js(和其他request.js不同)中:
引入文件依赖:
//创建axios实例
import axios from "axios";
// 导入 sm-crypto 库
import sm from "sm-crypto";
import CryptoJS from 'crypto-js'
import { jwtDecode } from "jwt-decode";
import { getToken, setToken, removeToken } from "@/utils/auth";
import { Notification, MessageBox, Message, Loading } from "element-ui";
import errorCode from "@/utils/errorCode";
import { eventBus } from '../../src/main';
请求拦截器:
// 请求拦截器
service.interceptors.request.use((config) => {// 在发送请求之前做些什么let accessToken = localStorage.getItem("accessToken");// console.log("key", accessToken);//解码密钥const jwtSecret = "8w9L95DwBCD9xjkR"; // 生成JWT密钥,固定数值// 解码JWT令牌获取参数// clientId secretKey secretValue timeEndconst decodedToken = jwtDecode(accessToken, jwtSecret);console.log(decodedToken);const clientId = decodedToken.clientId; //应用IDconsole.log("clientId");console.log(clientId);const secretKey = decodedToken.secretKey; //密钥const secretValue = decodedToken.secretValue; //密钥值const timeEnd = decodedToken.timeEnd; //获取key有效截止时间的时间戳const redirectUri = decodedToken.redirectUri ? decodedToken.redirectUri : null;window.redirectUri = redirectUri;window.clientId = clientId; // 存储在全局变量中// 通过事件总线发送事件通知eventBus.$emit('dataUpdated', {clientId,redirectUri});console.log("window.clientId");console.log(window.clientId);// 检查key是否有效if (timeEnd && Date.now() > Number(timeEnd)) {// key已过期,重新开始忘记密码流程return Promise.reject(); //超时,请求中断}const nonce = generateNonce(); // 生成随机字符串nonceconst timestamp = Date.now(); // 获取当前时间戳(毫秒级别)const sign = generateSign(nonce, timestamp, clientId, secretValue); // 生成签名sign// 生成加密keyconst payload = {clientId,timestamp,nonce,secretKey,sign,};const jwtToken = generateKey(payload, jwtSecret);// console.log("key");// console.log(jwtToken);config.headers["key"] = jwtToken; // 将key添加到请求头中并转换为字符串类型 // 将key添加到请求头中并转换为字符串类型return config;},(error) => {// 对请求错误做些什么return Promise.reject(error);}
);
// 生成随机字符串nonce的函数
function generateNonce() {const characters ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";const length = 16;let nonce = "";for (let i = 0; i < length; i++) {nonce += characters.charAt(Math.floor(Math.random() * characters.length));}return nonce;
}// 生成签名sign的函数
function generateSign(nonce, timestamp, clientId, secretValue) {const plaintext = `nonce=${nonce}×tamp=${timestamp}&clientId=${clientId}&secret=${secretValue}`;const sign = sm.sm3(plaintext); // 使用sm-crypto库中的sm3函数生成签名return sign;
}// 函数用于将对象编码为Base64格式
function encodeBase64(obj) {const stringifiedObj = JSON.stringify(obj);const wordArray = CryptoJS.enc.Utf8.parse(stringifiedObj);return CryptoJS.enc.Base64.stringify(wordArray).replace(/=+$/, "");
}// 生成JWT的函数
function generateJWT(payload, secret) {// 定义JWT的头部const header = { alg: "HS256", typ: "JWT" };// 编码头部和负载const encodedHeader = encodeBase64(header);const encodedPayload = encodeBase64(payload);// 生成签名const signature = CryptoJS.HmacSHA256(encodedHeader + "." + encodedPayload,secret);const signatureBase64 = CryptoJS.enc.Base64.stringify(signature).replace(/=+$/,"");const jwtToken = `${encodedHeader}.${encodedPayload}.${signatureBase64}`;return jwtToken;
}
// 生成key的函数
function generateKey(payload, jwtSecret) {const jwtToken = generateJWT(payload, jwtSecret);return jwtToken;
}
响应拦截器(无特殊处理):
function logOutWay() {MessageBox.confirm('未登录或登录已过期,请重新登录。', '系统提示', {confirmButtonText: '重新登录',showCancelButton: false,distinguishCancelAndClose: false,showClose: false,closeOnClickModal: false,type: 'warning'}).then(() => {// 跳转到登录页面let accessTokenKey = getToken()removeToken()localStorage.removeItem('accessToken')window.location.href = process.env.VUE_APP_BASE_JUMP+'auth/logout?accessToken=' + accessTokenKey + '&redirectUri='+process.env.VUE_APP_BASE_JUMP}).catch(() => {});}// 响应拦截器
service.interceptors.response.use((res) => {const isEncrypt = true;if (isEncrypt === "true" && !matchs(res.config.url, excludPtahs)) {//console.log("================解密===================")res.data = JSON.parse(interfaceDecryptSm2(res.data));}// console.log("拦截器===", res);// 未设置状态码则默认成功状态const code = res.data.code || 0;// 获取错误信息const msg = errorCode[code] || res.data.msg || errorCode["default"];// 二进制数据则直接返回if (res.request.responseType === "blob" ||res.request.responseType === "arraybuffer") {// console.log("1111===");return res.data;}if (code === 401) {} else if (code === 1) {if (msg && msg.includes("key不存在或已过期")) {logOutWay()// 中断请求return Promise.reject("无效的会话,或者会话已过期,请重新登录。");} else {Message({ message: msg, type: "error" });return Promise.reject(new Error(msg));}} else if (code === 601) {Message({ message: msg, type: "warning" });return Promise.reject("error");}// else if (code !== 0) {// Notification.error({ title: msg })// return Promise.reject('error')// }else {// console.log("22222===",res.data);return res.data;}},(error) => {console.log("err====" + error);let { message } = error;if (message == "Network Error") {message = "后端接口连接异常";} else if (message.includes("timeout")) {message = "系统接口请求超时";} else if (message.includes("Request failed with status code")) {if (message.substr(message.length - 3) == 401) {console.log("401走重新请求token逻辑");message = "未登录或登录已过期,请重新登录。";} else {message = "系统接口" + message.substr(message.length - 3) + "异常";}}Message({ message: message, type: "error", duration: 5 * 1000 });return Promise.reject(error);}
);//导入文件
export default service;