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) {