目录
设置找回密码请求拦截器
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 timeEnd
const decodedToken = jwtDecode(accessToken, jwtSecret);
console.log(decodedToken);
const clientId = decodedToken.clientId; //应用ID
console.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(); // 生成随机字符串nonce
const timestamp = Date.now(); // 获取当前时间戳(毫秒级别)
const sign = generateSign(nonce, timestamp, clientId, secretValue); // 生成签名sign
// 生成加密key
const 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;