Ver código fonte

refactor(登录验证系统): 优化验证方式,新增用户信息

1.新增加密系统,对用户信息进行加密储存
2.优化登录验证逻辑,防止越权
fxs 1 dia atrás
pai
commit
1d88eea6ed

+ 6 - 3
.env

@@ -1,10 +1,13 @@
 # 开发
-#VITE_API_URL_DEV="http://192.168.1.139:8000"
-VITE_API_URL_DEV='http://server.ichunhao.cn'
+VITE_API_URL_DEV="http://192.168.1.139:8000"
+#VITE_API_URL_DEV='http://server.ichunhao.cn'
 #VITE_API_URL_DEV="http://service.ichunhao.cn"
 # 测试服和本地开发
 #VITE_API_URL_TEST='http://server.ichunhao.cn'
 #VITE_API_URL_TEST="http://192.168.1.139:8000"
 VITE_API_URL_TEST="http://service.ichunhao.cn"
 # 线上
-VITE_API_URL_PRODUCT='http://service.ichunhao.cn'
+VITE_API_URL_PRODUCT='http://service.ichunhao.cn'
+
+# 加密秘钥
+VITE_SECRET_KEY='CHUNHAO-17492854'

+ 4 - 2
src/router/index.ts

@@ -6,9 +6,10 @@
  * @Description:
  *
  */
+
 import { createRouter, createWebHashHistory } from 'vue-router'
 
-import { authLogin } from '@/utils/auth/auth.ts'
+import { authLogin, clearClientInfo } from '@/utils/auth/auth.ts'
 
 import HomeRoutes from './home'
 import LoginRoutes from './login'
@@ -48,7 +49,8 @@ router.beforeEach((to, _from, next) => {
     if (authLogin()) {
       next()
     } else {
-      void router.push('login')
+      clearClientInfo()
+      void router.push('/login')
     }
   }
 })

+ 18 - 9
src/stores/useUser.ts

@@ -1,21 +1,30 @@
-import { defineStore } from 'pinia'
+/*
+ * @Author: fxs bjnsfxs@163.com
+ * @Date: 2024-08-22 10:05:10
+ * @LastEditors: fxs bjnsfxs@163.com
+ * @LastEditTime: 2024-10-15 11:24:59
+ * @FilePath: \Game-Backstage-Management-System\src\stores\useTable.ts
+ * @Description:
+ *
+ */
+
 import { ref } from 'vue'
+import { defineStore } from 'pinia'
 
 interface UserInfo {
+  userName: string
   isSuper: boolean
 }
 
-export const useUser = defineStore('userInfo', () => {
+export const useUser = defineStore('userInfoStore', () => {
   const userInfo = ref<UserInfo>({
+    userName: '',
     isSuper: false
   })
-
-  const setUserInfo = (info: UserInfo) => {
-    userInfo.value = info
-    console.log(userInfo.value)
-  }
-
   const getUserInfo = () => userInfo.value
 
-  return { setUserInfo, getUserInfo }
+  const updateUserInfo = (newUserInfo: UserInfo) => {
+    userInfo.value = newUserInfo
+  }
+  return { getUserInfo, updateUserInfo }
 })

+ 25 - 10
src/utils/auth/auth.ts

@@ -8,18 +8,33 @@
  *
  */
 
+import { removeAllToken } from '@/utils/token/token.ts'
 import { ElMessage } from 'element-plus'
 import { MessageType } from '@/types/res.ts'
-import { getLoginState } from '../localStorage/localStorage.ts'
+import {
+  clearUserInfo,
+  getLoginState,
+  getUserInfo,
+  setLoginState
+} from '../localStorage/localStorage.ts'
+
+/**
+ * @description: 登出
+ * @param msg 提示信息
+ */
+export const clearClientInfo = (msg: string = '请先登录') => {
+  ElMessage({
+    type: MessageType.Warning,
+    message: msg,
+    duration: 1500
+  })
+  removeAllToken()
+  setLoginState(false)
+  clearUserInfo()
+}
 
 export const authLogin = (): boolean => {
-  const state = getLoginState()
-  if (!state) {
-    ElMessage({
-      type: MessageType.Warning,
-      message: '请先登录',
-      duration: 1500
-    })
-  }
-  return state
+  const userInfo = getUserInfo()
+
+  return getLoginState() && !!userInfo
 }

+ 19 - 19
src/utils/axios/axiosInstance.ts

@@ -9,11 +9,11 @@
  */
 // 引入axios
 
+import { clearClientInfo } from '@/utils/auth/auth.ts'
 import { ElMessage } from 'element-plus'
 import { useRequest } from '@/hooks/useRequest'
 import { MessageType } from '@/types/res'
-import { getToken, refreshToken, removeAllToken, setToken } from '../token/token'
-import { setLoginState } from '../localStorage/localStorage'
+import { getToken, refreshToken, setToken } from '../token/token'
 
 import axios from 'axios'
 import router from '@/router'
@@ -37,20 +37,19 @@ const axiosInstance = axios.create({
 let isRefreshing = false // 是否正在刷新token
 let requestQueue: any[] = [] // 存储请求队列
 
-/**
- * @description: 未登录的情况下展示的信息
- * @param msg 提示信息
- */
-const showUncloggingInfo = (msg: string = '请先登录') => {
-  ElMessage({
-    type: MessageType.Warning,
-    message: msg,
-    duration: 1500
-  })
-  removeAllToken()
-  setLoginState(false)
-  void router.push('/login')
-}
+// /**
+//  * @description: 登出
+//  * @param msg 提示信息
+//  */
+// const clearClientInfo = (msg: string = '请先登录') => {
+//   ElMessage({
+//     type: MessageType.Warning,
+//     message: msg,
+//     duration: 1500
+//   })
+//   clearClientInfo()
+//   void router.push('/login')
+// }
 
 // 添加响应拦截器
 axiosInstance.interceptors.response.use(
@@ -59,7 +58,8 @@ axiosInstance.interceptors.response.use(
 
     // -2是token为空的情况
     if (code === -2) {
-      showUncloggingInfo()
+      clearClientInfo()
+      void router.push('/login')
     }
     // -1是token过期的情况
     if (code === -1) {
@@ -77,12 +77,12 @@ axiosInstance.interceptors.response.use(
               requestQueue = []
               return axiosInstance(config)
             } else {
-              showUncloggingInfo('登录已过期,请重新登陆')
+              clearClientInfo('登录已过期,请重新登陆')
             }
           })
           .catch((err) => {
             console.log(err)
-            showUncloggingInfo('登录已过期,请重新登陆')
+            clearClientInfo('登录已过期,请重新登陆')
           })
           .finally(() => {
             isRefreshing = false

+ 65 - 0
src/utils/encrypt/encrypt.ts

@@ -0,0 +1,65 @@
+// src/utils/crypto.ts
+import * as CryptoJS from 'crypto-js'
+
+/**
+ * AES加密工具类
+ * @description 使用CryptoJS实现数据加密/解密
+ * @reference https://cryptojs.gitbook.io/docs/
+ */
+class AESCipher {
+  private readonly key: CryptoJS.lib.WordArray
+  private readonly iv: CryptoJS.lib.WordArray
+
+  /**
+   * 初始化加密类
+   * @param secretKey - 加密密钥(建议长度>=16位)
+   * @param ivStr - 初始向量(可选,默认与secretKey相同)
+   */
+  constructor(secretKey: string, ivStr?: string) {
+    this.key = CryptoJS.enc.Utf8.parse(secretKey)
+    this.iv = CryptoJS.enc.Utf8.parse(ivStr || secretKey.slice(0, 16))
+  }
+
+  /**
+   * 加密数据
+   * @param data - 待加密数据
+   * @returns Base64编码的加密结果
+   */
+  encrypt(data: string): string {
+    const encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(data), this.key, {
+      iv: this.iv,
+      mode: CryptoJS.mode.CBC,
+      padding: CryptoJS.pad.Pkcs7
+    })
+    // 提取 ciphertext 并转为 Base64 [[1]]
+    return CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
+  }
+
+  /**
+   * 解密数据
+   * @param cipherText - 加密后的Base64字符串
+   * @returns 解密后的原始数据
+   */
+  decrypt(cipherText: string): string {
+    try {
+      // 先将 Base64 密文解析为 WordArray [[1]]
+      const cipherBytes = CryptoJS.enc.Base64.parse(cipherText)
+      // 转换为 Hex 格式(兼容 CryptoJS 解密逻辑)
+      const cipherWordArray = CryptoJS.format.Hex.parse(cipherBytes.toString())
+
+      const decrypted = CryptoJS.AES.decrypt(cipherWordArray, this.key, {
+        iv: this.iv,
+        mode: CryptoJS.mode.CBC,
+        padding: CryptoJS.pad.Pkcs7
+      })
+      return CryptoJS.enc.Utf8.stringify(decrypted) // 正确转换回字符串
+    } catch (error) {
+      console.error('Decryption failed:', error)
+      return ''
+    }
+  }
+}
+const key = import.meta.env.VITE_SECRET_KEY
+// 创建并导出单例实例
+const AesCipherInstance = new AESCipher(key) // 可以从环境变量中读取
+export default AesCipherInstance

+ 43 - 1
src/utils/localStorage/localStorage.ts

@@ -1,3 +1,9 @@
+import AesCipherInstance from '@/utils/encrypt/encrypt.ts'
+interface UserInfo {
+  userName: string
+  isSuper: boolean
+}
+
 /**
  * 从localstorage获取数据
  * @param {string} name localstorage的key
@@ -48,4 +54,40 @@ const getLoginState = (): boolean => {
   return JSON.parse(localStorage.getItem('loginState') as string) as boolean
 }
 
-export { getLocalInfo, saveLocalInfo, setLoginState, getLoginState }
+/**
+ * 清除用户信息
+ */
+const clearUserInfo = () => {
+  localStorage.removeItem('userInfo')
+}
+
+/**
+ * 保存用户信息
+ * @param userInfo 用户信息
+ */
+const setUserInfo = (userInfo: any) => {
+  const encryptInfo = AesCipherInstance.encrypt(JSON.stringify(userInfo))
+  localStorage.setItem('userInfo', encryptInfo)
+}
+
+/**
+ * 获取用户信息
+ */
+const getUserInfo = (): UserInfo | null => {
+  const info = localStorage.getItem('userInfo')
+  if (info) {
+    const decryptInfo = AesCipherInstance.decrypt(info)
+    return JSON.parse(decryptInfo) as UserInfo
+  }
+  return null
+}
+
+export {
+  getLocalInfo,
+  saveLocalInfo,
+  setLoginState,
+  getLoginState,
+  setUserInfo,
+  getUserInfo,
+  clearUserInfo
+}

+ 5 - 4
src/views/Login/LoginView.vue

@@ -7,14 +7,13 @@
  * 
 -->
 <script setup lang="ts">
-import { useUser } from '@/stores/useUser.ts'
 import type { RuleInfo } from '@/types/input'
 
 import { onMounted, reactive, ref } from 'vue'
 import { useRequest } from '@/hooks/useRequest'
 import { initLoadResource } from '@/utils/resource'
 import { setRefreshToken, setToken } from '@/utils/token/token'
-import { setLoginState } from '@/utils/localStorage/localStorage'
+import { setLoginState, setUserInfo } from '@/utils/localStorage/localStorage'
 
 import router from '@/router'
 import axiosInstance from '@/utils/axios/axiosInstance'
@@ -22,7 +21,6 @@ import MyButton from '@/components/form/MyButton.vue'
 import MyInput from '@/components/form/MyInput.vue'
 
 const { AllApi, analysisResCode } = useRequest()
-const { setUserInfo } = useUser()
 
 interface LoginInfoType {
   userName: string
@@ -113,7 +111,10 @@ const userLogin = async () => {
       setRefreshToken(result.refreshToken)
       setLoginState(true)
       // TODO 保存和验证用户信息
-      setUserInfo(result.isSuper)
+      setUserInfo({
+        userName: loginInfo.userName,
+        isSuper: result.isSuper
+      })
       axiosInstance.defaults.headers['Authorization'] = result.token // 需要在这里设置,不然又会触发拦截器
       void router.push('/')
     } catch (err) {