diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java index 8fb1d775..c015240f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java @@ -82,7 +82,6 @@ public class ShiroConfig { filterMap.put("/user/captcha", "anon"); filterMap.put("/user/smsVerification", "anon"); filterMap.put("/user/login", "anon"); - filterMap.put("/user/sm2-public-key", "anon"); filterMap.put("/user/pub-config", "anon"); filterMap.put("/user/register", "anon"); filterMap.put("/user/retrieve-password", "anon"); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java index 10d74045..4abd319b 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java @@ -87,47 +87,36 @@ public class LoginController { @PostMapping("/login") @Operation(summary = "登录") public Result login(@RequestBody LoginDTO login) { - // 验证是否正确输入验证码 - boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true); - if (!validate) { - throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR); - } - - String username = login.getUsername(); String password = login.getPassword(); - // 如果用户名是SM2加密格式,则进行解密 - if (isSM2Encrypted(username)) { - try { - // 获取SM2私钥 - String privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true); - if (StringUtils.isBlank(privateKeyStr)) { - throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED); - } - - // 使用SM2私钥解密用户名(十六进制格式) - String decryptedUsername = SM2Utils.decrypt(privateKeyStr, username); - login.setUsername(decryptedUsername); - } catch (Exception e) { - throw new RenException(ErrorCode.SM2_DECRYPT_ERROR); - } + // 获取SM2私钥 + String privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true); + if (StringUtils.isBlank(privateKeyStr)) { + throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED); } - // 如果密码是SM2加密格式,则进行解密 - if (isSM2Encrypted(password)) { - try { - // 获取SM2私钥 - String privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true); - if (StringUtils.isBlank(privateKeyStr)) { - throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED); - } - - // 使用SM2私钥解密密码 - String decryptedPassword = SM2Utils.decrypt(privateKeyStr, password); - login.setPassword(decryptedPassword); - } catch (Exception e) { - throw new RenException(ErrorCode.SM2_DECRYPT_ERROR); + // 使用SM2私钥解密密码 + String decryptedContent; + try { + decryptedContent = SM2Utils.decrypt(privateKeyStr, password); + } catch (Exception e) { + throw new RenException(ErrorCode.SM2_DECRYPT_ERROR); + } + + // 分离验证码和密码:前5位是验证码,后面是密码 + if (decryptedContent.length() > 5) { + String embeddedCaptcha = decryptedContent.substring(0, 5); + String actualPassword = decryptedContent.substring(5); + + // 验证嵌入的验证码是否正确 + boolean embeddedCaptchaValid = captchaService.validate(login.getCaptchaId(), embeddedCaptcha, true); + if (!embeddedCaptchaValid) { + throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR); } + + login.setPassword(actualPassword); + } else { + throw new RenException(ErrorCode.SM2_DECRYPT_ERROR); } // 按照用户名获取用户 @@ -143,21 +132,7 @@ public class LoginController { return sysUserTokenService.createToken(userDTO.getId()); } - /** - * 判断字符串是否为SM2加密格式 - * @param str 待判断的字符串 - * @return 是否为SM2加密格式 - */ - private boolean isSM2Encrypted(String str) { - if (StringUtils.isBlank(str)) { - return false; - } - // 十六进制格式检测(长度较长且只包含0-9,a-f,A-F字符) - if (str.length() > 100 && str.matches("^[0-9a-fA-F]+$")) { - return true; - } - return false; - } + @PostMapping("/register") @Operation(summary = "注册") @@ -166,64 +141,54 @@ public class LoginController { throw new RenException(ErrorCode.USER_REGISTER_DISABLED); } - String username = login.getUsername(); String password = login.getPassword(); - // 如果用户名是SM2加密格式,则进行解密 - if (isSM2Encrypted(username)) { - try { - // 获取SM2私钥 - String privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true); - if (StringUtils.isBlank(privateKeyStr)) { - throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED); - } - - // 使用SM2私钥解密用户名(十六进制格式) - String decryptedUsername = SM2Utils.decrypt(privateKeyStr, username); - login.setUsername(decryptedUsername); - } catch (Exception e) { - throw new RenException(ErrorCode.SM2_DECRYPT_ERROR); + // SM2加密 + String privateKeyStr; + try { + // 获取SM2私钥 + privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true); + if (StringUtils.isBlank(privateKeyStr)) { + throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED); } + } catch (Exception e) { + throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED); } - // 如果密码是SM2加密格式,则进行解密 - if (isSM2Encrypted(password)) { - try { - // 获取SM2私钥 - String privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true); - if (StringUtils.isBlank(privateKeyStr)) { - throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED); - } - - // 使用SM2私钥解密密码 - String decryptedPassword = SM2Utils.decrypt(privateKeyStr, password); - login.setPassword(decryptedPassword); - } catch (Exception e) { - throw new RenException(ErrorCode.SM2_DECRYPT_ERROR); + String decryptedContent; + try { + // 使用SM2私钥解密密码 + decryptedContent = SM2Utils.decrypt(privateKeyStr, password); + } catch (Exception e) { + throw new RenException(ErrorCode.SM2_DECRYPT_ERROR); + } + + // 分离验证码和密码:前5位是验证码,后面是密码 + if (decryptedContent.length() > 5) { + String embeddedCaptcha = decryptedContent.substring(0, 5); + String actualPassword = decryptedContent.substring(5); + + // 验证嵌入的验证码是否正确 + boolean embeddedCaptchaValid = captchaService.validate(login.getCaptchaId(), embeddedCaptcha, true); + if (!embeddedCaptchaValid) { + throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR); } + + login.setPassword(actualPassword); + } else { + throw new RenException(ErrorCode.SM2_DECRYPT_ERROR); } // 是否开启手机注册 Boolean isMobileRegister = sysParamsService .getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); - boolean validate; + if (isMobileRegister) { // 验证用户是否是手机号码 boolean validPhone = ValidatorUtils.isValidPhone(login.getUsername()); if (!validPhone) { throw new RenException(ErrorCode.USERNAME_NOT_PHONE); } - // 验证短信验证码是否正常 - validate = captchaService.validateSMSValidateCode(login.getUsername(), login.getMobileCaptcha(), false); - if (!validate) { - throw new RenException(ErrorCode.SMS_CODE_ERROR); - } - } else { - // 验证是否正确输入验证码 - validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true); - if (!validate) { - throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR); - } } // 按照用户名获取用户 @@ -304,18 +269,14 @@ public class LoginController { config.put("beianIcpNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_ICP_NUM.getValue(), true)); config.put("beianGaNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_GA_NUM.getValue(), true)); config.put("name", sysParamsService.getValue(Constant.SysBaseParam.SERVER_NAME.getValue(), true)); - - return new Result>().ok(config); - } - - @GetMapping("/sm2-public-key") - @Operation(summary = "获取SM2公钥") - public Result getSM2PublicKey() { - // 获取SM2公钥 + + // SM2公钥 String publicKey = sysParamsService.getValue(Constant.SM2_PUBLIC_KEY, true); if (StringUtils.isBlank(publicKey)) { throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED); } - return new Result().ok(publicKey); + config.put("sm2PublicKey", publicKey); + + return new Result>().ok(config); } } \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/messages_en_US.properties b/main/manager-api/src/main/resources/i18n/messages_en_US.properties index 05d05d0c..2df3a45f 100644 --- a/main/manager-api/src/main/resources/i18n/messages_en_US.properties +++ b/main/manager-api/src/main/resources/i18n/messages_en_US.properties @@ -134,4 +134,6 @@ 10123=Your MQTT secret is not secure, it needs to be at least 8 characters and must contain both uppercase and lowercase letters 10124=Your MQTT secret is not secure, MQTT secret must contain both uppercase and lowercase letters 10125=Your MQTT secret contains weak password -10128=Dictionary label is duplicated \ No newline at end of file +10128=Dictionary label is duplicated +10129=SM2 key not configured +10130=SM2 decryption failed \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties b/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties index 30bdb8d1..0c3df5cc 100644 --- a/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties +++ b/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties @@ -134,4 +134,6 @@ 10123=您的mqtt密钥长度不安全,字符数需要至少8个字符且必须同时包含大小写字母 10124=您的mqtt密钥长度不安全,mqtt密钥必须同时包含大小写字母 10125=您的mqtt密钥包含弱密码 -10128=字典标签重复 \ No newline at end of file +10128=字典标签重复 +10129=SM2密钥未配置 +10130=SM2解密失败 \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties b/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties index eb4e4ae5..4a1cda76 100644 --- a/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties +++ b/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties @@ -134,4 +134,6 @@ 10123=您的MQTT密鑰不安全,需至少8個字符且必須同時包含大小寫字母 10124=您的MQTT密鑰不安全,MQTT密鑰必須同時包含大小寫字母 10125=您的MQTT密鑰包含弱密碼 -10128=字典標籤重複 \ No newline at end of file +10128=字典標籤重複 +10129=SM2密鑰未配置 +10130=SM2解密失敗 \ No newline at end of file diff --git a/main/manager-web/package.json b/main/manager-web/package.json index 1ffd1c33..e9f0d65c 100644 --- a/main/manager-web/package.json +++ b/main/manager-web/package.json @@ -13,7 +13,6 @@ "dotenv": "^16.5.0", "element-ui": "^2.15.14", "flyio": "^0.6.14", - "jsbn": "^1.1.0", "normalize.css": "^8.0.1", "opus-decoder": "^0.7.7", "opus-recorder": "^8.0.5", diff --git a/main/manager-web/src/apis/module/user.js b/main/manager-web/src/apis/module/user.js index 78477cd1..c7290016 100755 --- a/main/manager-web/src/apis/module/user.js +++ b/main/manager-web/src/apis/module/user.js @@ -154,7 +154,7 @@ export default { }).send() }, // 获取公共配置 - getPubConfig(callback) { + getPubConfig(callback, failCallback) { RequestService.sendRequest() .url(`${getServiceUrl()}/user/pub-config`) .method('GET') @@ -162,10 +162,16 @@ export default { RequestService.clearRequestTime(); callback(res); }) + .fail((err) => { + RequestService.clearRequestTime(); + if (failCallback) { + failCallback(err); + } + }) .networkFail((err) => { console.error('获取公共配置失败:', err); RequestService.reAjaxFun(() => { - this.getPubConfig(callback); + this.getPubConfig(callback, failCallback); }); }).send(); }, @@ -193,23 +199,5 @@ export default { }); }).send() }, - // 获取SM2公钥 - getSM2PublicKey(callback, failCallback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/user/sm2-public-key`) - .method('GET') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail((err) => { - RequestService.clearRequestTime(); - failCallback(err); - }) - .networkFail(() => { - RequestService.reAjaxFun(() => { - this.getSM2PublicKey(callback, failCallback); - }); - }).send() - } + } diff --git a/main/manager-web/src/views/login.vue b/main/manager-web/src/views/login.vue index eeb6a89b..577069e8 100644 --- a/main/manager-web/src/views/login.vue +++ b/main/manager-web/src/views/login.vue @@ -148,7 +148,7 @@ import Api from "@/apis/api"; import VersionFooter from "@/components/VersionFooter.vue"; import i18n, { changeLanguage } from "@/i18n"; -import { getUUID, goToPage, showDanger, showSuccess, validateMobile, generateSm2KeyPairHex, sm2Encrypt, isBase64 } from "@/utils"; +import { getUUID, goToPage, showDanger, showSuccess, validateMobile, sm2Encrypt, isBase64 } from "@/utils"; import { mapState } from "vuex"; import Constant from "@/utils/constant"; @@ -198,7 +198,6 @@ export default { isMobileLogin: false, languageDropdownVisible: false, serverPublicKey: "", // 服务器公钥 - clientKeyPair: null, // 客户端密钥对 }; }, mounted() { @@ -209,8 +208,6 @@ export default { }); // 获取服务器公钥 this.getServerPublicKey(); - // 生成客户端密钥对 - this.generateClientKeyPair(); }, methods: { // 获取服务器公钥 @@ -225,30 +222,28 @@ export default { } console.log('本地存储无公钥,从服务器获取...'); - // 从服务器获取公钥 - Api.user.getSM2PublicKey( + // 从公共配置接口获取公钥 + Api.user.getPubConfig( (res) => { - if (res.data && res.data.data) { - console.log('获取到服务器公钥,长度:', res.data.data.length); - this.serverPublicKey = res.data.data; + if (res.data && res.data.data && res.data.data.sm2PublicKey) { + console.log('获取到服务器公钥,长度:', res.data.data.sm2PublicKey.length); + this.serverPublicKey = res.data.data.sm2PublicKey; // 存储到本地 localStorage.setItem(Constant.STORAGE_KEY.PUBLIC_KEY, this.serverPublicKey); console.log('公钥已存储到本地'); } else { console.error('服务器返回数据格式异常:', res); + showDanger(this.$t('sm2.failedToGetPublicKey')); } }, (err) => { - console.error("获取SM2公钥失败:", err); + console.error("获取公共配置失败:", err); showDanger(this.$t('sm2.failedToGetPublicKey')); } ); }, - // 生成客户端密钥对 - generateClientKeyPair() { - this.clientKeyPair = generateSm2KeyPairHex(); - }, + fetchCaptcha() { if (this.$store.getters.getToken) { @@ -357,36 +352,26 @@ export default { } // 加密密码 - let encryptedPassword = this.form.password; - if (!this.isSM2Encrypted(this.form.password)) { - try { - encryptedPassword = sm2Encrypt(this.serverPublicKey, this.form.password); - } catch (error) { - console.error("密码加密失败:", error); - showDanger(this.$t('sm2.encryptionFailed')); - return; - } + let encryptedPassword; + try { + // 拼接验证码和密码 + const captchaAndPassword = this.form.captcha + this.form.password; + encryptedPassword = sm2Encrypt(this.serverPublicKey, captchaAndPassword); + } catch (error) { + console.error("密码加密失败:", error); + showDanger(this.$t('sm2.encryptionFailed')); + return; } - // 加密用户名 - let encryptedUsername = this.form.username; - if (!this.isSM2Encrypted(this.form.username)) { - try { - encryptedUsername = sm2Encrypt(this.serverPublicKey, this.form.username); - } catch (error) { - console.error("用户名加密失败:", error); - showDanger(this.$t('sm2.encryptionFailed')); - return; - } - } + const plainUsername = this.form.username; this.form.captchaId = this.captchaUuid; - // 使用加密后的用户名和密码 + // 加密 const loginData = { - ...this.form, - username: encryptedUsername, - password: encryptedPassword + username: plainUsername, + password: encryptedPassword, + captchaId: this.form.captchaId }; Api.user.login( @@ -422,19 +407,6 @@ export default { }, goToForgetPassword() { goToPage("/retrieve-password"); - }, - - /** - * 判断字符串是否为SM2加密格式(十六进制格式) - * @param {string} str 待判断的字符串 - * @returns {boolean} 是否为SM2加密格式 - */ - isSM2Encrypted(str) { - if (typeof str !== 'string' || str.trim() === '') { - return false; - } - // 长度大于100且只包含0-9,a-f,A-F字符 - return str.length > 100 && /^[0-9a-fA-F]+$/.test(str); } }, }; diff --git a/main/manager-web/src/views/register.vue b/main/manager-web/src/views/register.vue index d8c4b83e..30168940 100644 --- a/main/manager-web/src/views/register.vue +++ b/main/manager-web/src/views/register.vue @@ -121,7 +121,7 @@