Files
labelmain-demo/components/login/libs/session.ts
2026-02-03 18:05:47 +08:00

157 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use server"
import { Base64 } from "js-base64"
import { headers } from "next/headers"
import redis from "./redis"
// 生成加密密钥
export async function generateKey(): Promise<CryptoKey> {
return crypto.subtle.generateKey(
{
name: "AES-CBC",
length: 256,
},
true,
["encrypt", "decrypt"]
)
}
export async function generateKeyFromEnv(
envPassword: string
): Promise<CryptoKey> {
const encoder = new TextEncoder()
const password = encoder.encode(envPassword)
const keyMaterial = await crypto.subtle.importKey(
"raw",
password,
{
name: "PBKDF2",
},
false,
["deriveKey"]
)
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: new Uint8Array(16), // 随机盐值
iterations: 100000, // 迭代次数
hash: "SHA-256",
},
keyMaterial,
{
name: "AES-CBC",
length: 256,
},
true,
["encrypt", "decrypt"]
)
}
// 将ArrayBuffer转换为Uint8Array
function arrayBufferToUint8Array(buffer: ArrayBuffer) {
return new Uint8Array(buffer)
}
// 加密函数
export async function encrypt(data: any, key: CryptoKey): Promise<string> {
const encodedData = new TextEncoder().encode(JSON.stringify(data))
const iv = crypto.getRandomValues(new Uint8Array(16)) // 16 bytes for AES block size
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-CBC",
iv: iv,
},
key,
encodedData
)
// 将Uint8Array转换为Base64字符串以便存储和传输
const encryptedBase64 = Base64.fromUint8Array(
arrayBufferToUint8Array(encrypted)
)
const ivBase64 = Base64.fromUint8Array(new Uint8Array(iv)) // 使用js-base64进行编码
// 将IV和加密数据拼接在一起以便解密时使用
return `${ivBase64}::${encryptedBase64}`
}
// 解密函数
export async function decrypt(
encryptedData: string,
key: CryptoKey
): Promise<any> {
// 将加密数据从Base64字符串转换回Uint8Array
const [ivBase64, encryptedBase64] = encryptedData.split("::")
const ivArray = Buffer.from(ivBase64, "base64")
const encrypted = Buffer.from(encryptedBase64, "base64")
// 使用AES-CBC算法进行解密
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: ivArray,
},
key,
encrypted
)
// 将ArrayBuffer转换为字符串
return new TextDecoder().decode(decrypted)
}
// 校验用户信息
export async function genAccessToken(req: any) {
try {
// 校验用户信息
// const fingerprint = req.headers.get("Fingerprint")
const sessionData = req.cookies.get("session")?.value
// 无session 直接重定向到登录页面
if (!sessionData) {
return "客户端请求中,未包含用户标识"
}
let redisKey = JSON.parse(sessionData)
const key = await generateKeyFromEnv(process.env.ENCRYPTION_KEY || "")
const decryptedSessionData = JSON.parse(await decrypt(redisKey, key))
const fingerprint = JSON.parse(decryptedSessionData)?.fingerprint
// 获取用户信息
let userData: any = await redis.get(redisKey)
userData = JSON.parse(userData)
// redis中无用户信息 直接重定向到登录页面
if (!userData) {
return "未获取到用户信息"
}
const header = headers()
let clientIP =
(await header).get("x-forwarded-for")?.split(",")[0] ||
req?.socket?.remoteAddress
// ip v6地址
if (clientIP.startsWith("::")) {
clientIP = clientIP.slice(7)
}
// 登录ip与用户请求ip不匹配
if (
clientIP !== userData?.clientIP ||
fingerprint !== userData?.fingerprint
) {
return `客户端信息与缓存用户信息不一致 \n 客户端: ${clientIP}, ${fingerprint} | redis存储: ${userData?.clientIP}, ${userData?.fingerprint}`
}
return {
...userData,
redisKey: sessionData,
}
} catch (err: any) {
return `获取用户信息报错, 报错原因:${err?.message}`
}
}