✨ add api validation signature
This commit is contained in:
@@ -9,25 +9,6 @@ export const getRotatedCaptchaData = () => {
|
|||||||
ignoreToken: true
|
ignoreToken: true
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* 验证验证码
|
|
||||||
* @param angle
|
|
||||||
* @param key
|
|
||||||
*/
|
|
||||||
export const checkRotatedCaptcha = (angle: any, key: any) => {
|
|
||||||
return service.Post('/api/captcha/rotate/check', {
|
|
||||||
angle: angle,
|
|
||||||
key: key,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
meta: {
|
|
||||||
ignoreToken: true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 获取滑动验证码图片数据
|
* 获取滑动验证码图片数据
|
||||||
|
@@ -35,17 +35,20 @@ export const refreshToken = (refreshToken: string) => {
|
|||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 发送短信验证码
|
* 发送短信验证码
|
||||||
* @param phone
|
* @param params
|
||||||
*/
|
*/
|
||||||
export const sendMessage = (phone: string) => {
|
export const sendMessage = (params: any) => {
|
||||||
return service.Get('/api/sms/test/send', {
|
return service.Post('/api/sms/test/send', {
|
||||||
params: {
|
phone: params.phone,
|
||||||
phone: phone
|
angle: params.angle,
|
||||||
|
key: params.key,
|
||||||
},
|
},
|
||||||
meta: {
|
{
|
||||||
ignoreToken: true
|
meta: {
|
||||||
|
ignoreToken: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 手机登录
|
* 手机登录
|
||||||
@@ -73,7 +76,9 @@ export const accountLoginApi = (param: AccountLogin) => {
|
|||||||
return service.Post('/api/user/login', {
|
return service.Post('/api/user/login', {
|
||||||
account: param.account,
|
account: param.account,
|
||||||
password: param.password,
|
password: param.password,
|
||||||
auto_login: param.auto_login
|
auto_login: param.auto_login,
|
||||||
|
angle: param.angle,
|
||||||
|
key: param.key,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
meta: {
|
meta: {
|
||||||
@@ -106,13 +111,14 @@ export const resetPasswordApi = (param: ResetPassword) => {
|
|||||||
* @param user_id
|
* @param user_id
|
||||||
*/
|
*/
|
||||||
export const getUserPermissions = (user_id: string) => {
|
export const getUserPermissions = (user_id: string) => {
|
||||||
return service.Get('/api/auth/permission/get_user_permissions', {
|
return service.Post('/api/auth/permission/get_user_permissions', {
|
||||||
params: {
|
|
||||||
user_id: user_id
|
user_id: user_id
|
||||||
},
|
},
|
||||||
meta: {
|
{
|
||||||
ignoreToken: false,
|
meta: {
|
||||||
|
ignoreToken: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
);
|
}
|
||||||
};
|
;
|
||||||
|
2
src/types/user.d.ts
vendored
2
src/types/user.d.ts
vendored
@@ -2,6 +2,8 @@ export interface AccountLogin {
|
|||||||
account?: string
|
account?: string
|
||||||
password?: string;
|
password?: string;
|
||||||
auto_login?: boolean;
|
auto_login?: boolean;
|
||||||
|
angle: number,
|
||||||
|
key: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PhoneLogin {
|
export interface PhoneLogin {
|
||||||
|
@@ -10,6 +10,7 @@ import {message, Modal} from "ant-design-vue";
|
|||||||
import i18n from "@/locales";
|
import i18n from "@/locales";
|
||||||
import {axiosRequestAdapter} from "@alova/adapter-axios";
|
import {axiosRequestAdapter} from "@alova/adapter-axios";
|
||||||
import {refreshToken} from "@/api/user";
|
import {refreshToken} from "@/api/user";
|
||||||
|
import createMD5Signature, {generateNonce} from "@/utils/signature/signature.ts";
|
||||||
|
|
||||||
let hasShownNetworkError: boolean = false;
|
let hasShownNetworkError: boolean = false;
|
||||||
const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication<typeof VueHook,
|
const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication<typeof VueHook,
|
||||||
@@ -59,6 +60,15 @@ export const service = createAlova({
|
|||||||
}
|
}
|
||||||
const lang = useStore().lang;
|
const lang = useStore().lang;
|
||||||
method.config.headers['Accept-Language'] = lang.lang || 'zh';
|
method.config.headers['Accept-Language'] = lang.lang || 'zh';
|
||||||
|
// 添加签名
|
||||||
|
if (method.type === 'POST') {
|
||||||
|
const nonce: string = generateNonce(); // 生成随机的 Nonce
|
||||||
|
const {signature, timestamp}: { signature: string, timestamp: number } = createMD5Signature(method, nonce);
|
||||||
|
method.config.headers['X-Sign'] = signature;
|
||||||
|
method.config.headers['X-Timestamp'] = timestamp;
|
||||||
|
method.config.headers['X-Nonce'] = nonce;
|
||||||
|
}
|
||||||
|
|
||||||
}),
|
}),
|
||||||
// 响应拦截器
|
// 响应拦截器
|
||||||
responded: onResponseRefreshToken({
|
responded: onResponseRefreshToken({
|
||||||
|
33
src/utils/signature/signature.ts
Normal file
33
src/utils/signature/signature.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import CryptoJS from 'crypto-js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 MD5 签名
|
||||||
|
* @param method
|
||||||
|
* @param nonce
|
||||||
|
*/
|
||||||
|
export default function createMD5Signature(method: any, nonce: string) {
|
||||||
|
const secretKey: string = "38h0ex04du8qqf9ar2knn1quicdsm4s0"; // 密钥
|
||||||
|
const timestamp: number = Date.now(); // 获取当前时间戳
|
||||||
|
const payload: string = JSON.stringify(method.data || {}); // 获取请求数据
|
||||||
|
|
||||||
|
// 创建待签名字符串
|
||||||
|
const baseString: string = `${method.type}:${payload}:${timestamp}:${nonce}:${secretKey}`;
|
||||||
|
|
||||||
|
// 生成 MD5 签名
|
||||||
|
const signature: string = CryptoJS.MD5(baseString).toString();
|
||||||
|
|
||||||
|
// 你可以根据需要返回包含时间戳的签名对象
|
||||||
|
return {
|
||||||
|
signature,
|
||||||
|
timestamp,
|
||||||
|
nonce
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机字符串作为 nonce
|
||||||
|
*/
|
||||||
|
export function generateNonce() {
|
||||||
|
return Math.random().toString(36).substring(2, 16); // 生成16位随机字符串
|
||||||
|
}
|
||||||
|
|
@@ -114,7 +114,7 @@ import {useRouter} from "vue-router";
|
|||||||
import {onMounted, reactive, ref, UnwrapRef} from "vue";
|
import {onMounted, reactive, ref, UnwrapRef} from "vue";
|
||||||
import {ResetPassword} from "@/types/user";
|
import {ResetPassword} from "@/types/user";
|
||||||
import {Rule} from "ant-design-vue/lib/form";
|
import {Rule} from "ant-design-vue/lib/form";
|
||||||
import {checkRotatedCaptcha, getRotatedCaptchaData} from "@/api/captcha";
|
import {getRotatedCaptchaData} from "@/api/captcha";
|
||||||
import {message} from "ant-design-vue";
|
import {message} from "ant-design-vue";
|
||||||
import {resetPasswordApi, sendMessage} from "@/api/user";
|
import {resetPasswordApi, sendMessage} from "@/api/user";
|
||||||
import {useDebounceFn} from "@vueuse/core";
|
import {useDebounceFn} from "@vueuse/core";
|
||||||
@@ -124,7 +124,6 @@ const {t} = useI18n();
|
|||||||
const resetPasswordRef = ref();
|
const resetPasswordRef = ref();
|
||||||
const captchaData = reactive({angle: 0, image: "", thumb: "", key: ""});
|
const captchaData = reactive({angle: 0, image: "", thumb: "", key: ""});
|
||||||
const showRotateCaptcha = ref<boolean>(false);
|
const showRotateCaptcha = ref<boolean>(false);
|
||||||
const captchaErrorCount = ref<number>(0);
|
|
||||||
const resetPasswordRotateEvent = {
|
const resetPasswordRotateEvent = {
|
||||||
confirm: (angle: number) => {
|
confirm: (angle: number) => {
|
||||||
checkPhoneLoginCaptcha(angle);
|
checkPhoneLoginCaptcha(angle);
|
||||||
@@ -293,9 +292,8 @@ async function getRotateCaptcha() {
|
|||||||
/**
|
/**
|
||||||
* 发送手机验证码
|
* 发送手机验证码
|
||||||
*/
|
*/
|
||||||
async function sendMessageByPhone(): Promise<boolean> {
|
async function sendMessageByPhone(param: any): Promise<boolean> {
|
||||||
const phone: string = ResetPasswordForm.phone as string;
|
const res: any = await sendMessage(param);
|
||||||
const res: any = await sendMessage(phone);
|
|
||||||
if (res.code === 200 && res.success) {
|
if (res.code === 200 && res.success) {
|
||||||
message.success(t('login.sendCaptchaSuccess'));
|
message.success(t('login.sendCaptchaSuccess'));
|
||||||
return true;
|
return true;
|
||||||
@@ -310,28 +308,15 @@ async function sendMessageByPhone(): Promise<boolean> {
|
|||||||
* @param angle
|
* @param angle
|
||||||
*/
|
*/
|
||||||
async function checkPhoneLoginCaptcha(angle: number) {
|
async function checkPhoneLoginCaptcha(angle: number) {
|
||||||
if (captchaErrorCount.value >= 2) {
|
const param = {
|
||||||
message.error(t('login.captchaError'));
|
key: captchaData.key,
|
||||||
getRotateCaptcha().then(() => {
|
angle: angle,
|
||||||
captchaErrorCount.value = 0;
|
phone: ResetPasswordForm.phone,
|
||||||
});
|
};
|
||||||
} else {
|
const result: boolean = await sendMessageByPhone(param);
|
||||||
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
|
if (result) {
|
||||||
if (result.code === 200 && result.success) {
|
showRotateCaptcha.value = false;
|
||||||
showRotateCaptcha.value = false;
|
countDown();
|
||||||
const result: boolean = await sendMessageByPhone();
|
|
||||||
if (result) {
|
|
||||||
countDown();
|
|
||||||
}
|
|
||||||
} else if (result.code === 1011) {
|
|
||||||
message.error(t('login.captchaExpired'));
|
|
||||||
getRotateCaptcha().then(() => {
|
|
||||||
captchaErrorCount.value = 0;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
captchaErrorCount.value++;
|
|
||||||
message.error(t('login.captchaError'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -193,7 +193,7 @@ import {useI18n} from "vue-i18n";
|
|||||||
import BoxDog from "@/components/BoxDog/BoxDog.vue";
|
import BoxDog from "@/components/BoxDog/BoxDog.vue";
|
||||||
import LoginFooter from "@/views/Login/LoginFooter.vue";
|
import LoginFooter from "@/views/Login/LoginFooter.vue";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {checkRotatedCaptcha, getRotatedCaptchaData} from "@/api/captcha";
|
import {getRotatedCaptchaData} from "@/api/captcha";
|
||||||
import {message} from "ant-design-vue";
|
import {message} from "ant-design-vue";
|
||||||
import {accountLoginApi, phoneLoginApi, sendMessage} from "@/api/user";
|
import {accountLoginApi, phoneLoginApi, sendMessage} from "@/api/user";
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
@@ -206,7 +206,6 @@ const phoneLoginFormRef = ref<any>();
|
|||||||
const showPhoneRotateCaptcha = ref<boolean>(false);
|
const showPhoneRotateCaptcha = ref<boolean>(false);
|
||||||
const showAccountRotateCaptcha = ref<boolean>(false);
|
const showAccountRotateCaptcha = ref<boolean>(false);
|
||||||
const captchaData = reactive({angle: 0, image: "", thumb: "", key: ""});
|
const captchaData = reactive({angle: 0, image: "", thumb: "", key: ""});
|
||||||
const captchaErrorCount = ref<number>(0);
|
|
||||||
const loginLoading = ref<boolean>(false);
|
const loginLoading = ref<boolean>(false);
|
||||||
const phoneLoginRotateEvent = {
|
const phoneLoginRotateEvent = {
|
||||||
confirm: (angle: number) => {
|
confirm: (angle: number) => {
|
||||||
@@ -235,6 +234,8 @@ const accountLoginForm: UnwrapRef<AccountLogin> = reactive({
|
|||||||
account: '',
|
account: '',
|
||||||
password: '',
|
password: '',
|
||||||
auto_login: true,
|
auto_login: true,
|
||||||
|
angle: 0,
|
||||||
|
key: "",
|
||||||
});
|
});
|
||||||
// 手机登录表单数据
|
// 手机登录表单数据
|
||||||
const phoneLoginForm: UnwrapRef<PhoneLogin> = reactive({
|
const phoneLoginForm: UnwrapRef<PhoneLogin> = reactive({
|
||||||
@@ -429,28 +430,15 @@ const checkPhoneLoginCaptchaDebounce = useDebounceFn(checkPhoneLoginCaptcha, 500
|
|||||||
* @param angle
|
* @param angle
|
||||||
*/
|
*/
|
||||||
async function checkPhoneLoginCaptcha(angle: number) {
|
async function checkPhoneLoginCaptcha(angle: number) {
|
||||||
if (captchaErrorCount.value >= 2) {
|
const params = {
|
||||||
message.error(t('login.captchaError'));
|
phone: phoneLoginForm.phone,
|
||||||
getRotateCaptcha().then(() => {
|
angle: angle,
|
||||||
captchaErrorCount.value = 0;
|
key: captchaData.key,
|
||||||
});
|
};
|
||||||
} else {
|
const result: boolean = await sendMessageByPhone(params);
|
||||||
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
|
if (result) {
|
||||||
if (result.code === 200 && result.success) {
|
showPhoneRotateCaptcha.value = false;
|
||||||
showPhoneRotateCaptcha.value = false;
|
countDown();
|
||||||
const result: boolean = await sendMessageByPhone();
|
|
||||||
if (result) {
|
|
||||||
countDown();
|
|
||||||
}
|
|
||||||
} else if (result.code === 1011) {
|
|
||||||
message.error(t('login.captchaExpired'));
|
|
||||||
getRotateCaptcha().then(() => {
|
|
||||||
captchaErrorCount.value = 0;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
captchaErrorCount.value++;
|
|
||||||
message.error(t('login.captchaError'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,51 +452,38 @@ const checkAccountLoginCaptchaDebounce = useDebounceFn(checkAccountLoginCaptcha,
|
|||||||
* @param angle
|
* @param angle
|
||||||
*/
|
*/
|
||||||
async function checkAccountLoginCaptcha(angle: number) {
|
async function checkAccountLoginCaptcha(angle: number) {
|
||||||
if (captchaErrorCount.value >= 2) {
|
const params = {
|
||||||
message.error(t('login.captchaError'));
|
...accountLoginForm,
|
||||||
getRotateCaptcha().then(() => {
|
angle: angle,
|
||||||
captchaErrorCount.value = 0;
|
key: captchaData.key,
|
||||||
});
|
};
|
||||||
|
loginLoading.value = true;
|
||||||
|
const res: any = await accountLoginApi(params);
|
||||||
|
if (res.code === 200 && res.success) {
|
||||||
|
const userStore = useStore().user;
|
||||||
|
const {uid, access_token, refresh_token, expires_at} = res.data;
|
||||||
|
userStore.user.uid = uid;
|
||||||
|
userStore.user.accessToken = access_token;
|
||||||
|
userStore.user.refreshToken = refresh_token;
|
||||||
|
userStore.user.expiresAt = expires_at;
|
||||||
|
message.success(t('login.loginSuccess'));
|
||||||
|
loginLoading.value = false;
|
||||||
|
showAccountRotateCaptcha.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push('/main');
|
||||||
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
|
loginLoading.value = false;
|
||||||
if (result.code === 200 && result.success) {
|
message.error(t('login.loginError'));
|
||||||
showAccountRotateCaptcha.value = false;
|
|
||||||
loginLoading.value = true;
|
|
||||||
const res: any = await accountLoginApi(accountLoginForm);
|
|
||||||
if (res.code === 200 && res.success) {
|
|
||||||
const userStore = useStore().user;
|
|
||||||
const {uid, access_token, refresh_token, expires_at} = res.data;
|
|
||||||
userStore.user.uid = uid;
|
|
||||||
userStore.user.accessToken = access_token;
|
|
||||||
userStore.user.refreshToken = refresh_token;
|
|
||||||
userStore.user.expiresAt = expires_at;
|
|
||||||
message.success(t('login.loginSuccess'));
|
|
||||||
loginLoading.value = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
router.push('/main');
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
loginLoading.value = false;
|
|
||||||
message.error(t('login.loginError'));
|
|
||||||
}
|
|
||||||
} else if (result.code === 1011) {
|
|
||||||
message.error(t('login.captchaExpired'));
|
|
||||||
getRotateCaptcha().then(() => {
|
|
||||||
captchaErrorCount.value = 0;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
captchaErrorCount.value++;
|
|
||||||
message.error(t('login.captchaError'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送手机验证码
|
* 发送手机验证码
|
||||||
*/
|
*/
|
||||||
async function sendMessageByPhone(): Promise<boolean> {
|
async function sendMessageByPhone(params: any): Promise<boolean> {
|
||||||
const phone: string = phoneLoginForm.phone as string;
|
const res: any = await sendMessage(params);
|
||||||
const res: any = await sendMessage(phone);
|
|
||||||
if (res.code === 200 && res.success) {
|
if (res.code === 200 && res.success) {
|
||||||
message.success(t('login.sendCaptchaSuccess'));
|
message.success(t('login.sendCaptchaSuccess'));
|
||||||
return true;
|
return true;
|
||||||
|
@@ -33,12 +33,12 @@ const wsOptions = {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
websocket.initialize(wsOptions);
|
websocket.initialize(wsOptions);
|
||||||
websocket.on("message", async (data: any) => {
|
websocket.on("message", async (data: any) => {
|
||||||
notification.open({
|
// notification.open({
|
||||||
message: '消息来了',
|
// message: '消息来了',
|
||||||
description:
|
// description:
|
||||||
data,
|
// data,
|
||||||
icon: () => h(SmileOutlined, {style: 'color: #108ee9'}),
|
// icon: () => h(SmileOutlined, {style: 'color: #108ee9'}),
|
||||||
});
|
// });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user