add api signature

This commit is contained in:
landaiqing
2024-11-19 01:48:32 +08:00
parent 23331318de
commit 5a05e87f49
17 changed files with 81 additions and 147 deletions

View File

@@ -19,3 +19,5 @@ VITE_APP_TOKEN_KEY='Bearer'
VITE_QR_SOCKET_URL='ws://127.0.0.1:80/api/ws/qrcode' VITE_QR_SOCKET_URL='ws://127.0.0.1:80/api/ws/qrcode'
VITE_MESSAGE_SOCKET_URL='ws://127.0.0.1:80/api/ws/message' VITE_MESSAGE_SOCKET_URL='ws://127.0.0.1:80/api/ws/message'
VITE_FINGERPRINT_KEY='idm0jdoau38lwourb4pbjk4dxkat0kcx'

View File

@@ -18,3 +18,5 @@ VITE_APP_TOKEN_KEY='Bearer'
VITE_QR_SOCKET_URL='wss://landaiqing.cn/api/ws/qr_ws' VITE_QR_SOCKET_URL='wss://landaiqing.cn/api/ws/qr_ws'
VITE_MESSAGE_SOCKET_URL='wss://landaiqing.cn/api/ws/message_ws' VITE_MESSAGE_SOCKET_URL='wss://landaiqing.cn/api/ws/message_ws'
# 签名密钥
VITE_FINGERPRINT_KEY='idm0jdoau38lwourb4pbjk4dxkat0kcx'

View File

@@ -31,6 +31,7 @@
"echarts": "^5.5.1", "echarts": "^5.5.1",
"eslint": "9.13.0", "eslint": "9.13.0",
"go-captcha-vue": "^2", "go-captcha-vue": "^2",
"jsencrypt": "^3.3.2",
"json-stringify-safe": "^5.0.1", "json-stringify-safe": "^5.0.1",
"less": "^4.2.0", "less": "^4.2.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",

View File

@@ -1,32 +1,15 @@
import {service} from "@/utils/alova/service.ts"; import {service} from "@/utils/alova/service.ts";
import {AccountLogin, PhoneLogin, ResetPassword} from "@/types/user"; import {AccountLogin, PhoneLogin, ResetPassword} from "@/types/user";
/**
* 获取用户信息
*/
export const getUserInfo = () => {
return service.Get('/api/auth/user/list', {
meta: {
ignoreToken: false
},
cacheFor: {
// 设置缓存模式为持久化模式
mode: 'restore',
// 缓存时间
expire: 1000 * 10,
tag: 'v1'
}
});
};
/** /**
* 刷新token * 刷新token
*/ */
export const refreshToken = () => { export const refreshToken = () => {
return service.Post('/api/token/refresh', {}, { return service.Post('/api/auth/token/refresh', {}, {
meta: { meta: {
authRole: 'refreshToken', authRole: 'refreshToken',
ignoreToken: false ignoreToken: false,
signature: true
} }
}); });
}; };
@@ -60,7 +43,8 @@ export const phoneLoginApi = (param: PhoneLogin) => {
{ {
meta: { meta: {
ignoreToken: true, ignoreToken: true,
authRole: 'login' authRole: 'login',
signature: true
} }
} }
); );
@@ -80,7 +64,8 @@ export const accountLoginApi = (param: AccountLogin) => {
{ {
meta: { meta: {
ignoreToken: true, ignoreToken: true,
authRole: 'login' authRole: 'login',
signature: true
} }
} }
); );
@@ -99,6 +84,7 @@ export const resetPasswordApi = (param: ResetPassword) => {
{ {
meta: { meta: {
ignoreToken: true, ignoreToken: true,
signature: true
} }
} }
); );
@@ -114,6 +100,7 @@ export const getUserDevice = () => {
params: {}, params: {},
meta: { meta: {
ignoreToken: true, ignoreToken: true,
signature: true
} }
} }
); );

View File

@@ -292,7 +292,7 @@ async function cancelCommentLike(item: any) {
* @param pageSize * @param pageSize
*/ */
async function paginationCommentChange(page: number, pageSize: number) { async function paginationCommentChange(page: number, pageSize: number) {
await router.push({ router.push({
path: "/main", path: "/main",
query: { query: {
type: router.currentRoute.value.query.type, type: router.currentRoute.value.query.type,
@@ -331,7 +331,7 @@ async function getLatestCommentList() {
query: { query: {
type: "latest", type: "latest",
page: router.currentRoute.value.query.page, page: router.currentRoute.value.query.page,
} }
}); });
comment.commentLoading = false; comment.commentLoading = false;
}); });

View File

@@ -1,4 +1,4 @@
const defaultImg: string = 'data:image/png;base64,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' const defaultImg: string = 'data:image/png;base64,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';
import {useIntersectionObserver} from '@vueuse/core'; import {useIntersectionObserver} from '@vueuse/core';

View File

@@ -5,14 +5,14 @@ import useStore from "@/store";
import {localforageStorageAdapter} from "@/utils/alova/adapter/localforageStorageAdapter.ts"; import {localforageStorageAdapter} from "@/utils/alova/adapter/localforageStorageAdapter.ts";
import {createServerTokenAuthentication} from "alova/client"; import {createServerTokenAuthentication} from "alova/client";
import {AxiosError, AxiosResponse} from "axios"; import {AxiosError, AxiosResponse} from "axios";
import {handleCode} from "@/utils/errorCode/errorCodeHandler.ts";
import {message, Modal} from "ant-design-vue"; import {message, Modal} from "ant-design-vue";
import i18n from "@/locales"; import i18n from "@/locales";
import {axiosRequestAdapter} from "@alova/adapter-axios"; import {axiosRequestAdapter} from "@alova/adapter-axios";
import {refreshToken} from "@/api/user"; import {refreshToken} from "@/api/user";
import createMD5Signature, {generateNonce} from "@/utils/signature/signature.ts"; import generateKeySecretSignature from "@/utils/signature/signature.ts";
import {handleErrorCode} from "@/utils/errorCode/errorCodeHandler.ts";
let hasShownNetworkError: boolean = false;
const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication<typeof VueHook, const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication<typeof VueHook,
typeof axiosRequestAdapter>({ typeof axiosRequestAdapter>({
refreshTokenOnSuccess: { refreshTokenOnSuccess: {
@@ -34,7 +34,7 @@ const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication
} }
}); });
export const service = createAlova({ export const service = createAlova({
timeout: 5000, timeout: 10000,
baseURL: import.meta.env.VITE_APP_BASE_API, baseURL: import.meta.env.VITE_APP_BASE_API,
statesHook: VueHook, statesHook: VueHook,
// 请求适配器 // 请求适配器
@@ -50,15 +50,9 @@ export const service = createAlova({
} }
const lang = useStore().lang; const lang = useStore().lang;
method.config.headers['Accept-Language'] = lang.lang || 'zh'; method.config.headers['Accept-Language'] = lang.lang || 'zh';
// 添加签名 if (method.meta?.signature) {
if (method.type === 'POST') { method.config.headers['X-Content-Security'] = generateKeySecretSignature(0, method.type, method.url, method.config.params, method.data);
const nonce: string = generateNonce(); // 生成随机的 Nonce
const {signature, timestamp}: { signature: string, timestamp: number } = createMD5Signature(method, nonce);
method.config.headers['X-Sign'] = signature;
method.config.headers['X-Timestamp'] = timestamp;
method.config.headers['X-Nonce'] = nonce;
} }
}), }),
// 响应拦截器 // 响应拦截器
responded: onResponseRefreshToken({ responded: onResponseRefreshToken({
@@ -77,11 +71,6 @@ export const service = createAlova({
window.location.href = '/login'; window.location.href = '/login';
}, 1000); }, 1000);
}, },
// onCancel() {
// setTimeout(() => {
// window.location.href = '/login';
// },2000);
// }
}); });
return Promise.reject(response.data); return Promise.reject(response.data);
} }
@@ -92,9 +81,8 @@ export const service = createAlova({
onError: onError:
(error: AxiosError, _method: any) => { (error: AxiosError, _method: any) => {
const {response} = error; const {response} = error;
if (response && !hasShownNetworkError) { if (response) {
hasShownNetworkError = true; handleErrorCode(response.status);
handleCode(response.status);
} }
if (!window.navigator.onLine) { if (!window.navigator.onLine) {
message.error(i18n.global.t('error.networkError')).then(); message.error(i18n.global.t('error.networkError')).then();

View File

@@ -1,64 +0,0 @@
/** @format */
import axios, {AxiosInstance, AxiosRequestConfig} from "axios";
import {message} from "ant-design-vue";
import useStore from "@/store";
import {handleCode} from "@/utils/errorCode/errorCodeHandler.ts";
class Request {
private instance: AxiosInstance | undefined;
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config);
// 全局请求拦截
this.instance.interceptors.request.use(
(config) => {
const user = useStore().user;
const token: string | undefined = user.user.accessToken;
if (token) {
config.headers.Authorization = `${import.meta.env.VITE_APP_TOKEN_KEY} ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
},
);
// 全局响应拦截
this.instance.interceptors.response.use(
(response) => {
if (response.data instanceof Blob) {
return response;
} else {
return response.data;
}
},
(error) => {
const {response} = error;
if (response) {
handleCode(response.status);
}
if (!window.navigator.onLine) {
message.error("网络连接失败");
return Promise.reject(error);
}
},
);
}
request<T>(config: AxiosRequestConfig<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
this.instance
?.request<T, T>(config)
.then((res) => {
resolve(res);
})
.catch((err) => {
reject(err);
});
});
}
}
export default Request;

View File

@@ -1,10 +0,0 @@
/** @format */
import Request from "./request";
const web: Request = new Request({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000,
});
export default web;

View File

@@ -1,6 +1,6 @@
import {message} from "ant-design-vue"; import {message} from "ant-design-vue";
import i18n from "@/locales"; import i18n from "@/locales";
export function handleCode(code: number): void { export function handleErrorCode(code: number): void {
switch (code) { switch (code) {
case 400: case 400:
message message

View File

@@ -1,33 +1,52 @@
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import JSEncrypt from 'jsencrypt';
const rsaPublicKey = "-----BEGIN PUBLIC KEY-----" +
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFe70Zi3OF7NuFi2saenJPjADW" +
"Ln402d142LOLBeN6cuWpItE3qgFsaMSorQApSM0recmAHMg4M4ly7+NgFPsaTzte" +
"MrO/LFCagwLWyyFJeqV4oQWRNQcFcGev8sTkUbIhhKpNAcmg37q8cmfI2eumycfl" +
"2FXuSyoJOa7hJgYNNQIDAQAB" +
"-----END PUBLIC KEY-----";
/** /**
* 生成 MD5 签名 * 生成前端密钥
* @param method * @param {string} type 请求类型0或1
* @param nonce * @param {string} method 请求方法(大写)
* @param {string} reqPath 请求路径不包含host
* @param {Object} reqQuery 请求参数
* @param {string} reqBody 请求体
*/ */
export default function createMD5Signature(method: any, nonce: string) { export default function generateKeySecretSignature(type: number, method: string, reqPath: string, reqQuery: any, reqBody: any): string {
const secretKey: string = "38h0ex04du8qqf9ar2knn1quicdsm4s0"; // 密钥 const time = (Date.now() / 1000).toFixed();
const timestamp: number = Date.now(); // 获取当前时间戳 const fingerprint = import.meta.env.VITE_FINGERPRINT_KEY as string;
const payload: string = JSON.stringify(method.data || {}); // 获取请求数据 // 生成 secret
const base64Key = btoa(fingerprint);
const secret = `type=${type};key=${base64Key};time=${time}`;
// 创建待签名字符串 const encryptor = new JSEncrypt();
const baseString: string = `${method.type}:${payload}:${timestamp}:${nonce}:${secretKey}`; encryptor.setPublicKey(rsaPublicKey);
const encryptedSecret = encryptor.encrypt(secret);
// 生成 MD5 签名 // 生成 signature
const signature: string = CryptoJS.MD5(baseString).toString(); const sha256Hash = CryptoJS.SHA256(typeof reqBody === 'object' ? JSON.stringify(reqBody) : reqBody).toString();
// 你可以根据需要返回包含时间戳的签名对象 const signContent = [
return { time,
signature, method,
timestamp, reqPath,
nonce isEmptyObject(reqQuery) ? '' : JSON.stringify(reqQuery),
}; sha256Hash
].join('\n');
const hmacHash = CryptoJS.HmacSHA256(signContent, fingerprint);
const signature = CryptoJS.enc.Base64.stringify(hmacHash);
return `key=${fingerprint};secret=${encryptedSecret};signature=${signature}`;
} }
/** /**
* 生成随机字符串作为 nonce * 判断对象是否为空
* @param obj
*/ */
export function generateNonce() { const isEmptyObject = (obj: any): boolean => {
return Math.random().toString(36).substring(2, 16); // 生成16位随机字符串 return obj && Object.keys(obj).length === 0 && obj.constructor === Object;
} };

View File

@@ -29,7 +29,7 @@ import {message} from "ant-design-vue";
import gitee from "@/assets/svgs/gitee.svg"; import gitee from "@/assets/svgs/gitee.svg";
import {getQQUrl} from "@/api/oauth/qq.ts"; import {getQQUrl} from "@/api/oauth/qq.ts";
import {useDebounceFn} from "@vueuse/core"; import {useDebounceFn} from "@vueuse/core";
import {generateClientId} from "@/api/oauth"; import {generateClientId} from "@/api/client";
import {getUserDevice} from "@/api/user"; import {getUserDevice} from "@/api/user";
const router = useRouter(); const router = useRouter();

View File

@@ -56,12 +56,12 @@ import BoxDog from "@/components/BoxDog/BoxDog.vue";
import QRLoginFooter from "@/views/QRLogin/QRLoginFooter.vue"; import QRLoginFooter from "@/views/QRLogin/QRLoginFooter.vue";
import {useRouter} from 'vue-router'; import {useRouter} from 'vue-router';
import {generateQrCode} from "@/api/oauth/wechat.ts"; import {generateQrCode} from "@/api/oauth/wechat.ts";
import {onMounted, ref} from "vue"; import {onBeforeUnmount, onMounted, ref} from "vue";
import logo from "@/assets/svgs/logo-schisandra.svg"; import logo from "@/assets/svgs/logo-schisandra.svg";
import useStore from "@/store"; import useStore from "@/store";
import {message} from "ant-design-vue"; import {message} from "ant-design-vue";
import {generateClientId} from "@/api/oauth"; import {generateClientId} from "@/api/client";
import {getUserDevice} from "@/api/user"; import {getUserDevice} from "@/api/user";
const {t} = useI18n(); const {t} = useI18n();
@@ -133,6 +133,9 @@ onMounted(async () => {
await getQrCode(); await getQrCode();
}); });
}); });
onBeforeUnmount(() => {
websocket.close(false);
});
</script> </script>
<style src="./index.scss" lang="scss" scoped> <style src="./index.scss" lang="scss" scoped>

View File

@@ -29,7 +29,7 @@ import {message} from "ant-design-vue";
import gitee from "@/assets/svgs/gitee.svg"; import gitee from "@/assets/svgs/gitee.svg";
import {getQQUrl} from "@/api/oauth/qq.ts"; import {getQQUrl} from "@/api/oauth/qq.ts";
import {useDebounceFn} from "@vueuse/core"; import {useDebounceFn} from "@vueuse/core";
import {generateClientId} from "@/api/oauth"; import {generateClientId} from "@/api/client";
import {getUserDevice} from "@/api/user"; import {getUserDevice} from "@/api/user";
const client = useStore().client; const client = useStore().client;

1
src/vite-env.d.ts vendored
View File

@@ -8,6 +8,7 @@ declare interface ImportMetaEnv {
readonly VITE_APP_TOKEN_KEY: string; readonly VITE_APP_TOKEN_KEY: string;
readonly VITE_QR_SOCKET_URL: string; readonly VITE_QR_SOCKET_URL: string;
readonly VITE_MESSAGE_SOCKET_URL: string; readonly VITE_MESSAGE_SOCKET_URL: string;
readonly VITE_FINGERPRINT_KEY: string;
} }
interface ImportMeta { interface ImportMeta {

View File

@@ -2989,6 +2989,11 @@ js-yaml@^4.1.0:
dependencies: dependencies:
argparse "^2.0.1" argparse "^2.0.1"
jsencrypt@^3.3.2:
version "3.3.2"
resolved "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.3.2.tgz#b0f1a2278810c7ba1cb8957af11195354622df7c"
integrity sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==
jsesc@^3.0.2: jsesc@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" resolved "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"