feat(components): 调整加密方式
This commit is contained in:
@@ -62,6 +62,7 @@
|
|||||||
"echarts": "6.0.0",
|
"echarts": "6.0.0",
|
||||||
"element-plus": "^2.11.1",
|
"element-plus": "^2.11.1",
|
||||||
"jsbarcode": "3.12.1",
|
"jsbarcode": "3.12.1",
|
||||||
|
"jsencrypt": "^3.5.4",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"pinia": "3.0.3",
|
"pinia": "3.0.3",
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export const locales = {
|
|||||||
},
|
},
|
||||||
gitCommitTypes: [
|
gitCommitTypes: [
|
||||||
['feat', '新功能'],
|
['feat', '新功能'],
|
||||||
['feat-wip', '开发中的功能,比如某功能的部分代码'],
|
|
||||||
['fix', '修复Bug'],
|
['fix', '修复Bug'],
|
||||||
['docs', '只涉及文档更新'],
|
['docs', '只涉及文档更新'],
|
||||||
['typo', '代码或文档勘误,比如错误拼写'],
|
['typo', '代码或文档勘误,比如错误拼写'],
|
||||||
@@ -49,7 +48,6 @@ export const locales = {
|
|||||||
},
|
},
|
||||||
gitCommitTypes: [
|
gitCommitTypes: [
|
||||||
['feat', 'A new feature'],
|
['feat', 'A new feature'],
|
||||||
['feat-wip', 'Features in development, such as partial code for a certain feature'],
|
|
||||||
['fix', 'A bug fix'],
|
['fix', 'A bug fix'],
|
||||||
['docs', 'Documentation only changes'],
|
['docs', 'Documentation only changes'],
|
||||||
['typo', 'Code or document corrections, such as spelling errors'],
|
['typo', 'Code or document corrections, such as spelling errors'],
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -83,6 +83,9 @@ importers:
|
|||||||
jsbarcode:
|
jsbarcode:
|
||||||
specifier: 3.12.1
|
specifier: 3.12.1
|
||||||
version: 3.12.1
|
version: 3.12.1
|
||||||
|
jsencrypt:
|
||||||
|
specifier: ^3.5.4
|
||||||
|
version: 3.5.4
|
||||||
json5:
|
json5:
|
||||||
specifier: 2.2.3
|
specifier: 2.2.3
|
||||||
version: 2.2.3
|
version: 2.2.3
|
||||||
@@ -3754,6 +3757,9 @@ packages:
|
|||||||
jsbarcode@3.12.1:
|
jsbarcode@3.12.1:
|
||||||
resolution: {integrity: sha512-QZQSqIknC2Rr/YOUyOkCBqsoiBAOTYK+7yNN3JsqfoUtJtkazxNw1dmPpxuv7VVvqW13kA3/mKiLq+s/e3o9hQ==}
|
resolution: {integrity: sha512-QZQSqIknC2Rr/YOUyOkCBqsoiBAOTYK+7yNN3JsqfoUtJtkazxNw1dmPpxuv7VVvqW13kA3/mKiLq+s/e3o9hQ==}
|
||||||
|
|
||||||
|
jsencrypt@3.5.4:
|
||||||
|
resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==}
|
||||||
|
|
||||||
jsesc@3.0.2:
|
jsesc@3.0.2:
|
||||||
resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
|
resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -9172,6 +9178,8 @@ snapshots:
|
|||||||
|
|
||||||
jsbarcode@3.12.1: {}
|
jsbarcode@3.12.1: {}
|
||||||
|
|
||||||
|
jsencrypt@3.5.4: {}
|
||||||
|
|
||||||
jsesc@3.0.2: {}
|
jsesc@3.0.2: {}
|
||||||
|
|
||||||
jsesc@3.1.0: {}
|
jsesc@3.1.0: {}
|
||||||
|
|||||||
121
src/service/request/api-encrypt.ts
Normal file
121
src/service/request/api-encrypt.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import type { InternalAxiosRequestConfig } from 'axios';
|
||||||
|
import { JSEncrypt } from 'jsencrypt';
|
||||||
|
import { SYSTEM_SERVICE_PREFIX } from '@/constants/service';
|
||||||
|
|
||||||
|
const API_ENCRYPT_HEADER = 'X-Api-Encrypt';
|
||||||
|
const API_ENCRYPT_HEADER_VALUE = 'true';
|
||||||
|
|
||||||
|
const API_ENCRYPT_CONFIG_ERROR_MSG = '前端加密配置异常,请联系管理员';
|
||||||
|
const API_ENCRYPT_REQUEST_ERROR_MSG = '请求加密失败,请刷新页面后重试';
|
||||||
|
|
||||||
|
const API_ENCRYPT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2kobVo5aSDWvmWZIKeL
|
||||||
|
G7dowOLTwjdWIwy7+UmTBw3e6vJW5BNEjrnW7kiqeT97VQj2V6MMmaJufpeegACG
|
||||||
|
AmhuTnG83kVLQeiXL5rlPUmdNPM8O89gSM3iMzLSUhn+rvAaHFXjKNu2xssodYn1
|
||||||
|
F26SlVO1ewwS82AAwEPSaotL7Kq8Qxg7vmZty6RcEjp7/OaYAtHfva3uewiGMp11
|
||||||
|
ZkywKPleQ3nT7HHjQgAckbNZFMhTMMqDzW5oI3KSm3sA+pWsUfRrZxUf2ws358/F
|
||||||
|
KewDbbhwj3u731NbXlO+WUfv3FvbdhktXtU/15FC0b+Tx+YHIUhkNTRzuIpiG7+X
|
||||||
|
cwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----`;
|
||||||
|
|
||||||
|
const API_ENCRYPT_RULE_MAP = {
|
||||||
|
[`POST ${SYSTEM_SERVICE_PREFIX}/auth/login`]: ['password'],
|
||||||
|
[`POST ${SYSTEM_SERVICE_PREFIX}/auth/register`]: ['password'],
|
||||||
|
[`PUT ${SYSTEM_SERVICE_PREFIX}/user/profile/update-password`]: ['oldPassword', 'newPassword'],
|
||||||
|
[`POST ${SYSTEM_SERVICE_PREFIX}/user/create`]: ['password'],
|
||||||
|
[`PUT ${SYSTEM_SERVICE_PREFIX}/user/update-password`]: ['password']
|
||||||
|
} as const satisfies Record<string, readonly string[]>;
|
||||||
|
|
||||||
|
type ApiEncryptRuleKey = keyof typeof API_ENCRYPT_RULE_MAP;
|
||||||
|
|
||||||
|
interface ApiEncryptRequestConfig extends InternalAxiosRequestConfig {
|
||||||
|
apiEncryptProcessed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let encryptor: JSEncrypt | null = null;
|
||||||
|
|
||||||
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Object]';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRequestPath(url: string) {
|
||||||
|
try {
|
||||||
|
return new URL(url, 'http://localhost').pathname;
|
||||||
|
} catch {
|
||||||
|
return url.split('?')[0] || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApiEncryptRule(config: InternalAxiosRequestConfig) {
|
||||||
|
const method = config.method?.toUpperCase();
|
||||||
|
const url = config.url;
|
||||||
|
|
||||||
|
if (!method || !url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleKey = `${method} ${normalizeRequestPath(url)}` as ApiEncryptRuleKey;
|
||||||
|
|
||||||
|
return API_ENCRYPT_RULE_MAP[ruleKey] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEncryptor() {
|
||||||
|
if (encryptor) {
|
||||||
|
return encryptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicKey = API_ENCRYPT_PUBLIC_KEY.trim();
|
||||||
|
|
||||||
|
if (!publicKey.includes('BEGIN PUBLIC KEY') || !publicKey.includes('END PUBLIC KEY')) {
|
||||||
|
throw new Error(API_ENCRYPT_CONFIG_ERROR_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptor = new JSEncrypt();
|
||||||
|
encryptor.setPublicKey(publicKey);
|
||||||
|
|
||||||
|
return encryptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encryptFieldValue(value: unknown) {
|
||||||
|
if (typeof value !== 'string' || value.length === 0) {
|
||||||
|
throw new Error(API_ENCRYPT_REQUEST_ERROR_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptedValue = getEncryptor().encrypt(value);
|
||||||
|
|
||||||
|
if (!encryptedValue) {
|
||||||
|
throw new Error(API_ENCRYPT_REQUEST_ERROR_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyApiEncrypt(config: InternalAxiosRequestConfig) {
|
||||||
|
const encryptConfig = config as ApiEncryptRequestConfig;
|
||||||
|
|
||||||
|
if (encryptConfig.apiEncryptProcessed) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptFields = getApiEncryptRule(config);
|
||||||
|
|
||||||
|
if (!encryptFields) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPlainObject(config.data)) {
|
||||||
|
throw new Error(API_ENCRYPT_REQUEST_ERROR_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextData = { ...config.data };
|
||||||
|
|
||||||
|
encryptFields.forEach(field => {
|
||||||
|
nextData[field] = encryptFieldValue(nextData[field]);
|
||||||
|
});
|
||||||
|
|
||||||
|
config.data = nextData;
|
||||||
|
config.headers.set(API_ENCRYPT_HEADER, API_ENCRYPT_HEADER_VALUE);
|
||||||
|
encryptConfig.apiEncryptProcessed = true;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { useAuthStore } from '@/store/modules/auth';
|
|||||||
import { localStg } from '@/utils/storage';
|
import { localStg } from '@/utils/storage';
|
||||||
import { getServiceBaseURL } from '@/utils/service';
|
import { getServiceBaseURL } from '@/utils/service';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
|
import { applyApiEncrypt } from './api-encrypt';
|
||||||
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
|
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
|
||||||
import type { RequestInstanceState } from './type';
|
import type { RequestInstanceState } from './type';
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ export const request = createFlatRequest(
|
|||||||
async onRequest(config) {
|
async onRequest(config) {
|
||||||
const Authorization = getAuthorization();
|
const Authorization = getAuthorization();
|
||||||
Object.assign(config.headers, { Authorization });
|
Object.assign(config.headers, { Authorization });
|
||||||
|
applyApiEncrypt(config);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user