mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
update:非对称加密注册功能
This commit is contained in:
+38
@@ -165,6 +165,44 @@ public class LoginController {
|
|||||||
if (!sysUserService.getAllowUserRegister()) {
|
if (!sysUserService.getAllowUserRegister()) {
|
||||||
throw new RenException(ErrorCode.USER_REGISTER_DISABLED);
|
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加密格式,则进行解密
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 是否开启手机注册
|
// 是否开启手机注册
|
||||||
Boolean isMobileRegister = sysParamsService
|
Boolean isMobileRegister = sysParamsService
|
||||||
.getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class);
|
.getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class);
|
||||||
|
|||||||
@@ -121,8 +121,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import Api from '@/apis/api';
|
import Api from '@/apis/api';
|
||||||
import VersionFooter from '@/components/VersionFooter.vue';
|
import VersionFooter from '@/components/VersionFooter.vue';
|
||||||
import { getUUID, goToPage, showDanger, showSuccess, validateMobile } from '@/utils';
|
import { getUUID, goToPage, showDanger, showSuccess, validateMobile, generateSm2KeyPairHex, sm2Encrypt, isBase64 } from '@/utils';
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex';
|
||||||
|
import Constant from '@/utils/constant';
|
||||||
|
|
||||||
// 导入语言切换功能
|
// 导入语言切换功能
|
||||||
import { changeLanguage } from '@/i18n';
|
import { changeLanguage } from '@/i18n';
|
||||||
@@ -156,7 +157,10 @@ export default {
|
|||||||
},
|
},
|
||||||
captchaUrl: '',
|
captchaUrl: '',
|
||||||
countdown: 0,
|
countdown: 0,
|
||||||
timer: null
|
timer: null,
|
||||||
|
serverPublicKey: "", // 服务器公钥
|
||||||
|
clientKeyPair: null, // 客户端密钥对
|
||||||
|
isGettingPublicKey: false // 获取公钥状态
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -169,8 +173,49 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.fetchCaptcha();
|
this.fetchCaptcha();
|
||||||
|
// 获取服务器公钥
|
||||||
|
this.getServerPublicKey();
|
||||||
|
// 生成客户端密钥对
|
||||||
|
this.generateClientKeyPair();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 获取服务器公钥
|
||||||
|
getServerPublicKey() {
|
||||||
|
console.log('开始获取服务器公钥...');
|
||||||
|
// 先从本地存储获取
|
||||||
|
const storedPublicKey = localStorage.getItem(Constant.STORAGE_KEY.PUBLIC_KEY);
|
||||||
|
if (storedPublicKey) {
|
||||||
|
console.log('从本地存储获取到公钥,长度:', storedPublicKey.length);
|
||||||
|
this.serverPublicKey = storedPublicKey;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('本地存储无公钥,从服务器获取...');
|
||||||
|
// 从服务器获取公钥
|
||||||
|
Api.user.getSM2PublicKey(
|
||||||
|
(res) => {
|
||||||
|
if (res.data && res.data.data) {
|
||||||
|
console.log('获取到服务器公钥,长度:', res.data.data.length);
|
||||||
|
this.serverPublicKey = res.data.data;
|
||||||
|
// 存储到本地
|
||||||
|
localStorage.setItem(Constant.STORAGE_KEY.PUBLIC_KEY, this.serverPublicKey);
|
||||||
|
console.log('公钥已存储到本地');
|
||||||
|
} else {
|
||||||
|
console.error('服务器返回数据格式异常:', res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.error("获取SM2公钥失败:", err);
|
||||||
|
showDanger(this.$t('sm2.failedToGetPublicKey'));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生成客户端密钥对
|
||||||
|
generateClientKeyPair() {
|
||||||
|
this.clientKeyPair = generateSm2KeyPairHex();
|
||||||
|
},
|
||||||
|
|
||||||
// 复用验证码获取方法
|
// 复用验证码获取方法
|
||||||
fetchCaptcha() {
|
fetchCaptcha() {
|
||||||
this.form.captchaId = getUUID();
|
this.form.captchaId = getUUID();
|
||||||
@@ -240,7 +285,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 注册逻辑
|
// 注册逻辑
|
||||||
register() {
|
async register() {
|
||||||
if (this.enableMobileRegister) {
|
if (this.enableMobileRegister) {
|
||||||
// 手机号注册验证
|
// 手机号注册验证
|
||||||
if (!validateMobile(this.form.mobile, this.form.areaCode)) {
|
if (!validateMobile(this.form.mobile, this.form.areaCode)) {
|
||||||
@@ -271,11 +316,71 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.enableMobileRegister) {
|
// 检查服务器公钥是否已获取,如果未获取则重新获取
|
||||||
this.form.username = this.form.areaCode + this.form.mobile
|
if (!this.serverPublicKey) {
|
||||||
|
try {
|
||||||
|
// 等待公钥获取完成
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
this.getServerPublicKey();
|
||||||
|
// 设置超时检查,最多等待3秒
|
||||||
|
const checkInterval = setInterval(() => {
|
||||||
|
if (this.serverPublicKey) {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
if (!this.serverPublicKey) {
|
||||||
|
reject(new Error('获取公钥超时'));
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
showDanger(this.$t('sm2.failedToGetPublicKey'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Api.user.register(this.form, ({ data }) => {
|
// 加密密码
|
||||||
|
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 encryptedUsername = this.form.username;
|
||||||
|
if (this.enableMobileRegister) {
|
||||||
|
this.form.username = this.form.areaCode + this.form.mobile;
|
||||||
|
encryptedUsername = this.form.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isSM2Encrypted(encryptedUsername)) {
|
||||||
|
try {
|
||||||
|
encryptedUsername = sm2Encrypt(this.serverPublicKey, encryptedUsername);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("用户名加密失败:", error);
|
||||||
|
showDanger(this.$t('sm2.encryptionFailed'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备注册数据
|
||||||
|
const registerData = {
|
||||||
|
...this.form,
|
||||||
|
username: encryptedUsername,
|
||||||
|
password: encryptedPassword,
|
||||||
|
confirmPassword: encryptedPassword
|
||||||
|
};
|
||||||
|
|
||||||
|
Api.user.register(registerData, ({ data }) => {
|
||||||
showSuccess(this.$t('register.registerSuccess'))
|
showSuccess(this.$t('register.registerSuccess'))
|
||||||
goToPage('/login')
|
goToPage('/login')
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
@@ -288,6 +393,19 @@ export default {
|
|||||||
|
|
||||||
goToLogin() {
|
goToLogin() {
|
||||||
goToPage('/login')
|
goToPage('/login')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字符串是否为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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
|||||||
Reference in New Issue
Block a user