From b833138e27f2be1d8296270aa04a888070bf407a Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Thu, 9 Apr 2026 16:20:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(X-Pack):=20=E6=94=AF=E6=8C=81=20Hmac-auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/ehcache/ehcache.xml | 4 + .../components/plugin/src/ImportXpackTool.ts | 73 +++++++++++++++++++ core/core-frontend/src/config/axios/hmac.ts | 18 +++++ .../core-frontend/src/config/axios/service.ts | 2 + core/core-frontend/src/locales/en.ts | 4 + core/core-frontend/src/locales/tw.ts | 4 + core/core-frontend/src/locales/zh-CN.ts | 4 + .../setting/api/PerSettingApi.java | 17 +++++ .../io/dataease/constant/CacheConstant.java | 1 + .../main/java/io/dataease/utils/AesUtils.java | 29 ++++++++ .../io/dataease/utils/WhitelistUtils.java | 1 + 11 files changed, 157 insertions(+) create mode 100644 core/core-frontend/src/components/plugin/src/ImportXpackTool.ts create mode 100644 core/core-frontend/src/config/axios/hmac.ts diff --git a/core/core-backend/src/main/resources/ehcache/ehcache.xml b/core/core-backend/src/main/resources/ehcache/ehcache.xml index a5eabbba46..3403f02679 100644 --- a/core/core-backend/src/main/resources/ehcache/ehcache.xml +++ b/core/core-backend/src/main/resources/ehcache/ehcache.xml @@ -98,6 +98,10 @@ java.lang.String java.lang.Object + + java.lang.String + java.lang.Object + java.lang.String java.lang.Object diff --git a/core/core-frontend/src/components/plugin/src/ImportXpackTool.ts b/core/core-frontend/src/components/plugin/src/ImportXpackTool.ts new file mode 100644 index 0000000000..487767dc6b --- /dev/null +++ b/core/core-frontend/src/components/plugin/src/ImportXpackTool.ts @@ -0,0 +1,73 @@ +import { xpackModelApi } from '@/api/plugin' +import { useCache } from '@/hooks/web/useCache' +import { isNull } from '@/utils/utils' +import { useEmbedded } from '@/store/modules/embedded' +const { wsCache } = useCache() +const embeddedStore = useEmbedded() +const basePath = import.meta.env.VITE_API_BASEPATH +const embeddedBasePath = + basePath.startsWith('./') && basePath.length > 2 ? basePath.substring(2) : basePath +export const PATH_URL = embeddedStore.baseUrl ? embeddedStore?.baseUrl + embeddedBasePath : basePath + +const remoteTsUrl = PATH_URL + '/DEXPackTs.umd.js' +const loadXpackTs = (url: string) => { + return new Promise(function (resolve, reject) { + const scriptId = 'de-fit2cloud-script-xpack-ts-id' + let dom = document.getElementById(scriptId) + if (dom) { + dom.parentElement?.removeChild(dom) + dom = null + } + const script = document.createElement('script') + + script.id = scriptId + script.onload = function () { + return resolve() + } + script.onerror = function () { + return reject(new Error('Load script from '.concat(url, ' failed'))) + } + script.src = url + const head = document.head || document.getElementsByTagName('head')[0] + ;(document.body || head).appendChild(script) + }) +} + +export const importXpackTool = async (methodName: string) => { + const jsname = 'L3Rvb2xzL0htYWNUb29s' + const key = 'xpack-model-distributed' + let distributed = false + if (wsCache.get(key) === null) { + const res = await xpackModelApi() + const resData = isNull(res.data) ? 'null' : res.data + wsCache.set('xpack-model-distributed', resData) + distributed = res.data + } else { + distributed = wsCache.get(key) + } + distributed = true + if (isNull(distributed) || !distributed) { + return null + } + if (window['DEXPackTs']) { + const xpack = await window['DEXPackTs'].mapping[jsname] + return xpack[methodName] + } else { + if (!window['de_hmac_promise']) { + window['de_hmac_promise'] = null + } + if (!window['de_hmac_promise']) { + window['de_hmac_promise'] = loadXpackTs(remoteTsUrl) + .then(() => { + return window['DEXPackTs'] + }) + .catch((error: any) => { + window['__de_xpack_loading__'] = null + throw error + }) + } + await window['de_hmac_promise'] + const xpack = await window['DEXPackTs'].mapping[jsname] + return xpack[methodName] + } +} diff --git a/core/core-frontend/src/config/axios/hmac.ts b/core/core-frontend/src/config/axios/hmac.ts new file mode 100644 index 0000000000..bb8252ead0 --- /dev/null +++ b/core/core-frontend/src/config/axios/hmac.ts @@ -0,0 +1,18 @@ +import { importXpackTool } from '@/components/plugin/src/ImportXpackTool' + +const hmac_white_list = ['/xpackModel'] +export const securityConfig = async (config: any, requestPath: string) => { + if (hmac_white_list.some(item => requestPath.includes(item))) { + return + } + try { + const method = await importXpackTool('securityConfig') + if (!method) { + return + } + return method(config, requestPath) + } catch (error) { + console.error('Failed to load securityConfig method:', error) + return + } +} diff --git a/core/core-frontend/src/config/axios/service.ts b/core/core-frontend/src/config/axios/service.ts index fbeed02f9a..f86c189ef3 100644 --- a/core/core-frontend/src/config/axios/service.ts +++ b/core/core-frontend/src/config/axios/service.ts @@ -17,6 +17,7 @@ import { configHandler } from './refresh' import { isMobile, getLocale } from '@/utils/utils' import { useRequestStoreWithOut } from '@/store/modules/request' import { clearCache } from '@/utils/cacheUtil' +import { securityConfig } from './hmac' type AxiosErrorWidthLoading = T & { config: { @@ -100,6 +101,7 @@ service.interceptors.request.use( if (config instanceof Promise) { config = await config } + await securityConfig(config, service.getUri(config)) if ( config.method === 'post' && (config.headers as AxiosRequestHeaders)['Content-Type'] === diff --git a/core/core-frontend/src/locales/en.ts b/core/core-frontend/src/locales/en.ts index d101882ffb..9d89c42534 100644 --- a/core/core-frontend/src/locales/en.ts +++ b/core/core-frontend/src/locales/en.ts @@ -4814,6 +4814,10 @@ export default { security: { title: 'Security Settings' }, + setting_hmac: { + title: 'HMAC Settings', + enable: 'Enable HMAC Authentication' + }, setting_mfa: { title: 'MFA Settings', status: 'Global MFA Authentication Enabled', diff --git a/core/core-frontend/src/locales/tw.ts b/core/core-frontend/src/locales/tw.ts index 9a04c7a010..f501c4ebbf 100644 --- a/core/core-frontend/src/locales/tw.ts +++ b/core/core-frontend/src/locales/tw.ts @@ -4670,6 +4670,10 @@ export default { security: { title: '安全設置' }, + setting_hmac: { + title: 'HMAC 設定', + enable: '啟用 HMAC 認證' + }, setting_mfa: { title: 'MFA 設置', status: '全局啟用 MFA 認證', diff --git a/core/core-frontend/src/locales/zh-CN.ts b/core/core-frontend/src/locales/zh-CN.ts index abc7fc0396..35103c6ff1 100644 --- a/core/core-frontend/src/locales/zh-CN.ts +++ b/core/core-frontend/src/locales/zh-CN.ts @@ -4679,6 +4679,10 @@ export default { security: { title: '安全设置' }, + setting_hmac: { + title: 'HMAC 设置', + enable: '启用 HMAC 认证' + }, setting_mfa: { title: 'MFA 设置', status: '全局启用 MFA 认证', diff --git a/sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/setting/api/PerSettingApi.java b/sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/setting/api/PerSettingApi.java index 489aca93cd..d83fed52bf 100644 --- a/sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/setting/api/PerSettingApi.java +++ b/sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/setting/api/PerSettingApi.java @@ -41,4 +41,21 @@ public interface PerSettingApi { @Operation(summary = "查询MFA状态") @GetMapping("/mfaStatus") Integer mfaStatus(); + + + @Operation(summary = "查询Hmac设置") + @GetMapping("/hmac/query") + List hmacSetting(); + + @Operation(summary = "保存Hmac设置") + @PostMapping("/hmac/save") + void saveHmac(@RequestBody List settings); + + @Hidden + @GetMapping("/hmac/info") + String hmacInfo(); + + @Hidden + @PostMapping("/hmac/refresh") + String refreshHmacSecret(); } diff --git a/sdk/common/src/main/java/io/dataease/constant/CacheConstant.java b/sdk/common/src/main/java/io/dataease/constant/CacheConstant.java index 5202b22702..ba58e7918d 100644 --- a/sdk/common/src/main/java/io/dataease/constant/CacheConstant.java +++ b/sdk/common/src/main/java/io/dataease/constant/CacheConstant.java @@ -31,6 +31,7 @@ public class CacheConstant { public static final String RSA_CACHE = "de_v2_rsa"; public static final String PER_MENU_ID_CACHE = "de_v2_per_menu_id"; public static final String GLOBAL_MFA_CACHE = "de_v2_global_mfa"; + public static final String GLOBAL_HMAC_CACHE = "de_v2_global_hmac"; } public static class LicenseCacheConstant { diff --git a/sdk/common/src/main/java/io/dataease/utils/AesUtils.java b/sdk/common/src/main/java/io/dataease/utils/AesUtils.java index ed7d454ed5..f4d55d7c71 100644 --- a/sdk/common/src/main/java/io/dataease/utils/AesUtils.java +++ b/sdk/common/src/main/java/io/dataease/utils/AesUtils.java @@ -9,6 +9,8 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import java.security.SecureRandom; + import static java.nio.charset.StandardCharsets.UTF_8; public class AesUtils { @@ -62,4 +64,31 @@ public class AesUtils { public static Object aesDecrypt(Object o) { return o == null ? null : aesDecrypt(o.toString(), "www.fit2cloud.co", "1234567890123456"); } + + public static String aesEncryptWithIv(String src, String secretKey) { + if (StringUtils.isBlank(secretKey)) { + throw new RuntimeException("secretKey is empty"); + } + try { + // 生成随机IV + byte[] iv = new byte[16]; + new SecureRandom().nextBytes(iv); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + byte[] raw = secretKey.getBytes(UTF_8); + SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec); + byte[] encrypted = cipher.doFinal(src.getBytes(UTF_8)); + + // 将IV拼接到密文前面 + byte[] ivAndCipher = new byte[iv.length + encrypted.length]; + System.arraycopy(iv, 0, ivAndCipher, 0, iv.length); + System.arraycopy(encrypted, 0, ivAndCipher, iv.length, encrypted.length); + + return Base64.encodeBase64String(ivAndCipher); + } catch (Exception e) { + throw new RuntimeException("AES encrypt error:", e); + } + } } diff --git a/sdk/common/src/main/java/io/dataease/utils/WhitelistUtils.java b/sdk/common/src/main/java/io/dataease/utils/WhitelistUtils.java index 5d0a50d0d1..8fab4b1911 100644 --- a/sdk/common/src/main/java/io/dataease/utils/WhitelistUtils.java +++ b/sdk/common/src/main/java/io/dataease/utils/WhitelistUtils.java @@ -50,6 +50,7 @@ public class WhitelistUtils { "/embedded/initIframe", "/sysParameter/i18nOptions", "/login/modifyInvalidPwd", + "/perSetting/hmac/info", "/"); public static boolean match(String requestURI) {