Procházet zdrojové kódy

!967 锁屏密码通过后端进行校验
Merge pull request !967 from KaneLeung/next

zuohuaijun před 2 roky
rodič
revize
55c7b10f3b

+ 55 - 0
Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs

@@ -113,6 +113,61 @@ public class SysAuthService : IDynamicApiController, ITransient
         return await CreateToken(user);
     }
 
+	/// <summary>
+	/// 锁屏验证账号密码
+	/// </summary>
+	/// <param name="password"></param>
+	/// <remarks>用户名/密码:superadmin/123456</remarks>
+	/// <returns></returns>
+	[DisplayName("锁屏验证账号密码")]
+	public async Task<bool> Unlock([Required,FromQuery] string password)
+	{
+		// 判断密码错误次数(默认5次,缓存30分钟)
+		var keyErrorPasswordCount = $"{CacheConst.KeyErrorPasswordCount}{_userManager.Account}";
+		var errorPasswordCount = _sysCacheService.Get<int>(keyErrorPasswordCount);
+		if (errorPasswordCount >= 5)
+			throw Oops.Oh(ErrorCodeEnum.D1027);
+
+		// 账号是否存在
+		var user = await _sysUserRep.GetFirstAsync(u => u.Id == _userManager.UserId);
+		_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
+
+		// 账号是否被冻结
+		if (user.Status == StatusEnum.Disable)
+			throw Oops.Oh(ErrorCodeEnum.D1017);
+
+		// 租户是否被禁用
+		var tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Id == user.TenantId);
+		if (tenant != null && tenant.Status == StatusEnum.Disable)
+			throw Oops.Oh(ErrorCodeEnum.Z1003);
+
+		// 国密SM2解密(前端密码传输SM2加密后的)
+		password = CryptogramUtil.SM2Decrypt(password);
+
+		// 密码是否正确
+		if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString())
+		{
+			if (!user.Password.Equals(MD5Encryption.Encrypt(password)))
+			{
+				_sysCacheService.Set(keyErrorPasswordCount, ++errorPasswordCount, TimeSpan.FromMinutes(30));
+				throw Oops.Oh(ErrorCodeEnum.D1000);
+			}
+		}
+		else
+		{
+			if (!CryptogramUtil.Decrypt(user.Password).Equals(password))
+			{
+				_sysCacheService.Set(keyErrorPasswordCount, ++errorPasswordCount, TimeSpan.FromMinutes(30));
+				throw Oops.Oh(ErrorCodeEnum.D1000);
+			}
+		}
+
+		// 登录成功则清空密码错误次数
+		_sysCacheService.Remove(keyErrorPasswordCount);
+
+		return true;
+	}
+
     /// <summary>
     /// 手机号登录
     /// </summary>

+ 81 - 71
Web/src/api-services/apis/sys-auth-api.ts

@@ -11,12 +11,12 @@
  * https://github.com/swagger-api/swagger-codegen.git
  * Do not edit the class manually.
  */
-
 import globalAxios, { AxiosResponse, AxiosInstance, AxiosRequestConfig } from 'axios';
 import { Configuration } from '../configuration';
 // Some imports not used depending on template conditions
 // @ts-ignore
 import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base';
+import { AdminResultBoolean } from '../models';
 import { AdminResultLoginOutput } from '../models';
 import { AdminResultLoginUserOutput } from '../models';
 import { AdminResultObject } from '../models';
@@ -48,13 +48,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarQueryParameter = {} as any;
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
             const query = new URLSearchParams(localVarUrlObj.search);
             for (const key in localVarQueryParameter) {
@@ -91,13 +84,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarQueryParameter = {} as any;
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
             const query = new URLSearchParams(localVarUrlObj.search);
             for (const key in localVarQueryParameter) {
@@ -139,13 +125,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarQueryParameter = {} as any;
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
             localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
 
@@ -191,13 +170,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarQueryParameter = {} as any;
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
             localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
 
@@ -238,13 +210,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarQueryParameter = {} as any;
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
             const query = new URLSearchParams(localVarUrlObj.search);
             for (const key in localVarQueryParameter) {
@@ -282,13 +247,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarQueryParameter = {} as any;
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
             if (accessToken !== undefined) {
                 localVarQueryParameter['accessToken'] = accessToken;
@@ -310,6 +268,51 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
                 options: localVarRequestOptions,
             };
         },
+        /**
+         * 用户名/密码:superadmin/123456
+         * @summary 锁屏验证账号密码
+         * @param {string} password 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysAuthUnlockPost: async (password: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'password' is not null or undefined
+            if (password === null || password === undefined) {
+                throw new RequiredError('password','Required parameter password was null or undefined when calling apiSysAuthUnlockPost.');
+            }
+            const localVarPath = `/api/sysAuth/unlock`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+
+            if (password !== undefined) {
+                localVarQueryParameter['password'] = password;
+            }
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
         /**
          * 
          * @summary 获取登录账号
@@ -329,13 +332,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarQueryParameter = {} as any;
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
             const query = new URLSearchParams(localVarUrlObj.search);
             for (const key in localVarQueryParameter) {
@@ -372,13 +368,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarQueryParameter = {} as any;
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
             const query = new URLSearchParams(localVarUrlObj.search);
             for (const key in localVarQueryParameter) {
@@ -415,13 +404,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarQueryParameter = {} as any;
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
             const query = new URLSearchParams(localVarUrlObj.search);
             for (const key in localVarQueryParameter) {
@@ -461,13 +443,6 @@ export const SysAuthApiAxiosParamCreator = function (configuration?: Configurati
             const localVarFormParams = new FormData();
 
             // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
 
 
             if (userName !== undefined) { 
@@ -586,6 +561,20 @@ export const SysAuthApiFp = function(configuration?: Configuration) {
                 return axios.request(axiosRequestArgs);
             };
         },
+        /**
+         * 用户名/密码:superadmin/123456
+         * @summary 锁屏验证账号密码
+         * @param {string} password 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysAuthUnlockPost(password: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultBoolean>>> {
+            const localVarAxiosArgs = await SysAuthApiAxiosParamCreator(configuration).apiSysAuthUnlockPost(password, options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
         /**
          * 
          * @summary 获取登录账号
@@ -706,6 +695,16 @@ export const SysAuthApiFactory = function (configuration?: Configuration, basePa
         async apiSysAuthRefreshTokenGet(accessToken?: string, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultString>> {
             return SysAuthApiFp(configuration).apiSysAuthRefreshTokenGet(accessToken, options).then((request) => request(axios, basePath));
         },
+        /**
+         * 用户名/密码:superadmin/123456
+         * @summary 锁屏验证账号密码
+         * @param {string} password 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysAuthUnlockPost(password: string, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultBoolean>> {
+            return SysAuthApiFp(configuration).apiSysAuthUnlockPost(password, options).then((request) => request(axios, basePath));
+        },
         /**
          * 
          * @summary 获取登录账号
@@ -817,6 +816,17 @@ export class SysAuthApi extends BaseAPI {
     public async apiSysAuthRefreshTokenGet(accessToken?: string, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultString>> {
         return SysAuthApiFp(this.configuration).apiSysAuthRefreshTokenGet(accessToken, options).then((request) => request(this.axios, this.basePath));
     }
+    /**
+     * 用户名/密码:superadmin/123456
+     * @summary 锁屏验证账号密码
+     * @param {string} password 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysAuthApi
+     */
+    public async apiSysAuthUnlockPost(password: string, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultBoolean>> {
+        return SysAuthApiFp(this.configuration).apiSysAuthUnlockPost(password, options).then((request) => request(this.axios, this.basePath));
+    }
     /**
      * 
      * @summary 获取登录账号

+ 78 - 13
Web/src/layout/lockScreen/index.vue

@@ -28,16 +28,15 @@
 				<div v-show="state.isShowLoockLogin" class="layout-lock-screen-login">
 					<div class="layout-lock-screen-login-box">
 						<div class="layout-lock-screen-login-box-img">
-							<img src="https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500" />
+							<img :src="userInfos.avatar || 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500'" />
 						</div>
-						<div class="layout-lock-screen-login-box-name">Administrator</div>
-						<div class="layout-lock-screen-login-box-value">
-							<el-input
-								placeholder="请输入密码"
-								ref="layoutLockScreenInputRef"
-								v-model="state.lockScreenPassword"
-								@keyup.enter.native.stop="onLockScreenSubmit()"
-							>
+						<div class="layout-lock-screen-login-box-name">{{ userInfos.account }}</div>
+						<div v-if="state.showMessage" class="layout-lock-screen-login-box-message">
+							<span>{{ state.message }}</span>
+							<el-button style="max-width: 80px; margin-top: 20px" size="default" @click="hideMessage"> 确认 </el-button>
+						</div>
+						<div v-else class="layout-lock-screen-login-box-value">
+							<el-input placeholder="请输入密码" type="password" ref="layoutLockScreenInputRef" size="default" v-model="state.lockScreenPassword" @keyup.enter.native.stop="onLockScreenSubmit()">
 								<template #append>
 									<el-button @click="onLockScreenSubmit">
 										<el-icon class="el-input__icon">
@@ -65,12 +64,18 @@ import { formatDate } from '/@/utils/formatTime';
 import { Local } from '/@/utils/storage';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
+import { useUserInfo } from '/@/stores/userInfo';
+import { sm2 } from 'sm-crypto-v2';
+import { feature, getAPI } from '/@/utils/axios-utils';
+import { SysAuthApi } from '/@/api-services';
 
 // 定义变量内容
 const layoutLockScreenDateRef = ref<HtmlType>();
 const layoutLockScreenInputRef = ref();
 const storesThemeConfig = useThemeConfig();
 const { themeConfig } = storeToRefs(storesThemeConfig);
+const storesUserInfo = useUserInfo();
+const { userInfos } = storeToRefs(storesUserInfo);
 const state = reactive({
 	transparency: 1,
 	downClientY: 0,
@@ -87,6 +92,8 @@ const state = reactive({
 	isShowLockScreen: false,
 	isShowLockScreenIntervalTime: 0,
 	lockScreenPassword: '',
+	message: '',
+	showMessage: false,
 });
 
 // 鼠标按下 pc
@@ -177,16 +184,62 @@ const setLocalThemeConfig = () => {
 	Local.set('themeConfig', themeConfig.value);
 };
 // 密码输入点击事件
-const onLockScreenSubmit = () => {
-	themeConfig.value.isLockScreen = false;
-	themeConfig.value.lockScreenTime = 30;
-	setLocalThemeConfig();
+const onLockScreenSubmit = async () => {
+	if (state.lockScreenPassword) {
+		try {
+			// SM2加密密码
+			// const keys = SM2.generateKeyPair();
+			const publicKey = `0484C7466D950E120E5ECE5DD85D0C90EAA85081A3A2BD7C57AE6DC822EFCCBD66620C67B0103FC8DD280E36C3B282977B722AAEC3C56518EDCEBAFB72C5A05312`;
+			const password = sm2.doEncrypt(state.lockScreenPassword, publicKey, 1);
+			const [err, res] = await feature(getAPI(SysAuthApi).apiSysAuthUnlockPost(password));
+			if (err) {
+				console.log(err);
+				state.message = err.message;
+				state.showMessage = true;
+				state.lockScreenPassword = '';
+
+				return;
+			}
+			if (res.data.result) {
+				themeConfig.value.isLockScreen = false;
+				themeConfig.value.lockScreenTime = 30;
+				setLocalThemeConfig();
+			}
+		} catch (ex: any) {
+			state.message = `出错了:${ex}`;
+			state.showMessage = true;
+		}
+	}
+};
+//隐藏消息
+const hideMessage = () => {
+	state.showMessage = false;
+	nextTick(() => {
+		layoutLockScreenInputRef.value.focus();
+	});
 };
 // 页面加载时
 onMounted(() => {
 	initGetElement();
 	initSetTime();
 	initLockScreen();
+	//侦听ENTER按钮事件
+	document.onkeydown = (e) => {
+		if (e.key === 'Enter') {
+			//当显示锁屏页时,按ENTER切到密码输入
+			if (state.isShowLoockLogin == false) {
+				const moveInterval = setInterval(() => {
+					state.isFlags = true;
+					state.moveDifference = state.moveDifference - 10;
+					onMove();
+					//超过600像素则结束
+					if (state.moveDifference < -600) clearInterval(moveInterval);
+				}, 5);
+			}
+			//当显示消息时,按ENTER切到密码输入
+			if (state.showMessage == true) hideMessage();
+		}
+	};
 });
 // 页面卸载时
 onUnmounted(() => {
@@ -322,6 +375,15 @@ onUnmounted(() => {
 				font-size: 26px;
 				margin: 15px 0 30px;
 			}
+			&-message {
+				font-size: 16px;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+			}
+			&-value {
+				min-height: 73px;
+			}
 		}
 		&-icon {
 			position: absolute;
@@ -343,6 +405,9 @@ onUnmounted(() => {
 	background: var(--el-color-white);
 	padding: 0px 15px;
 }
+:deep(.el-input__wrapper.is-focus) {
+	box-shadow: unset !important;
+}
 :deep(.el-input__inner) {
 	border-right-color: var(--el-border-color-extra-light);
 	&:hover {