feat: 加强安全性 传输过程使用salt sha256

This commit is contained in:
手瓜一十雪
2025-04-19 19:50:52 +08:00
parent b1047309c9
commit fda050d3fe
6 changed files with 40 additions and 17 deletions

View File

@@ -55,6 +55,7 @@
"ahooks": "^3.8.4",
"axios": "^1.7.9",
"clsx": "^2.1.1",
"crypto-js": "^4.2.0",
"echarts": "^5.5.1",
"event-source-polyfill": "^1.0.31",
"framer-motion": "^12.0.6",
@@ -88,6 +89,7 @@
"@eslint/js": "^9.19.0",
"@react-types/shared": "^3.26.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/crypto-js": "^4.2.2",
"@types/event-source-polyfill": "^1.0.5",
"@types/fabric": "^5.3.9",
"@types/node": "^22.12.0",

View File

@@ -3,7 +3,7 @@ import { EventSourcePolyfill } from 'event-source-polyfill'
import { LogLevel } from '@/const/enum'
import { serverRequest } from '@/utils/request'
import CryptoJS from "crypto-js";
export interface Log {
level: LogLevel
message: string
@@ -17,9 +17,10 @@ export default class WebUIManager {
}
public static async loginWithToken(token: string) {
const sha256 = CryptoJS.SHA256(token + '.napcat').toString();
const { data } = await serverRequest.post<ServerResponse<AuthResponse>>(
'/auth/login',
{ token }
{ hash: sha256 }
)
return data.data.Credential
}

View File

@@ -20,25 +20,26 @@ export const CheckDefaultTokenHandler: RequestHandler = async (_, res) => {
export const LoginHandler: RequestHandler = async (req, res) => {
// 获取WebUI配置
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
// 获取请求体中的token
const { token } = req.body;
// 获取请求体中的hash
const { hash } = req.body;
// 获取客户端IP
const clientIP = req.ip || req.socket.remoteAddress || '';
// 如果token为空返回错误信息
if (isEmpty(token)) {
if (isEmpty(hash)) {
return sendError(res, 'token is empty');
}
// 检查登录频率
if (!WebUiDataRuntime.checkLoginRate(clientIP, WebUiConfigData.loginRate)) {
return sendError(res, 'login rate limit');
}
//验证config.token是否等于token
if (WebUiConfigData.token !== token) {
//验证config.token hash是否等于token hash
if (!AuthHelper.comparePasswordHash(WebUiConfigData.token, hash)) {
return sendError(res, 'token is invalid');
}
// 签发凭证
const signCredential = Buffer.from(JSON.stringify(AuthHelper.signCredential(WebUiConfigData.token))).toString(
const signCredential = Buffer.from(JSON.stringify(AuthHelper.signCredential(hash))).toString(
'base64'
);
// 返回成功信息

View File

@@ -5,13 +5,13 @@ export class AuthHelper {
/**
* 签名凭证方法。
* @param token 待签名的凭证字符串。
* @param hash 待签名的凭证字符串。
* @returns 签名后的凭证对象。
*/
public static signCredential(token: string): WebUiCredentialJson {
public static signCredential(hash: string): WebUiCredentialJson {
const innerJson: WebUiCredentialInnerJson = {
CreatedTime: Date.now(),
TokenEncoded: token,
HashEncoded: hash,
};
const jsonString = JSON.stringify(innerJson);
const hmac = crypto.createHmac('sha256', AuthHelper.secretKey).update(jsonString, 'utf8').digest('hex');
@@ -57,8 +57,7 @@ export class AuthHelper {
const currentTime = Date.now() / 1000;
const createdTime = credentialJson.Data.CreatedTime;
const timeDifference = currentTime - createdTime;
return timeDifference <= 3600 && credentialJson.Data.TokenEncoded === token;
return timeDifference <= 3600 && credentialJson.Data.HashEncoded === AuthHelper.generatePasswordHash(token);
}
/**
@@ -85,4 +84,23 @@ export class AuthHelper {
return store.exists(`revoked:${hmac}`) > 0;
}
/**
* 生成密码Hash
* @param password 密码
* @returns 生成的Hash值
*/
public static generatePasswordHash(password: string): string {
return crypto.createHash('sha256').update(password + '.napcat').digest().toString('hex')
}
/**
* 对比密码和Hash值
* @param password 密码
* @param hash Hash值
* @returns 布尔值表示密码是否匹配Hash值
*/
public static comparePasswordHash(password: string, hash: string): boolean {
return this.generatePasswordHash(password) === hash;
}
}

View File

@@ -21,17 +21,18 @@ export async function auth(req: Request, res: Response, next: NextFunction) {
return sendError(res, 'Unauthorized');
}
// 获取token
const token = authorization[1];
const hash = authorization[1];
if(!hash) return sendError(res, 'Unauthorized');
// 解析token
let Credential: WebUiCredentialJson;
try {
Credential = JSON.parse(Buffer.from(token, 'base64').toString('utf-8'));
Credential = JSON.parse(Buffer.from(hash, 'base64').toString('utf-8'));
} catch (e) {
return sendError(res, 'Unauthorized');
}
// 获取配置
const config = await WebUiConfig.GetWebUIConfig();
// 验证凭证在1小时内有效且token与原始token相同
// 验证凭证在1小时内有效
const credentialJson = AuthHelper.validateCredentialWithinOneHour(config.token, Credential);
if (credentialJson) {
// 通过验证

View File

@@ -1,6 +1,6 @@
interface WebUiCredentialInnerJson {
CreatedTime: number;
TokenEncoded: string;
HashEncoded: string;
}
interface WebUiCredentialJson {