支持Authorization头认证

This commit is contained in:
mtvpls
2026-01-25 16:07:56 +08:00
parent 267258f66c
commit 29781d9d5a
4 changed files with 93 additions and 41 deletions
-1
View File
@@ -262,7 +262,6 @@ export async function GET(request: NextRequest) {
response.cookies.set('oidc_session', JSON.stringify(oidcSession), {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 600, // 10分钟
});
-1
View File
@@ -47,7 +47,6 @@ export async function GET(request: NextRequest) {
response.cookies.set('oidc_state', state, {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 600, // 10分钟
});
+26 -12
View File
@@ -1,6 +1,7 @@
/* eslint-disable no-console,@typescript-eslint/no-explicit-any */
import { NextRequest, NextResponse } from 'next/server';
import { parseAuthInfo } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db';
import {
@@ -21,6 +22,21 @@ const STORAGE_TYPE =
| 'kvrocks'
| undefined) || 'localstorage';
function buildLoginResponse(authToken?: string | null) {
const body: Record<string, unknown> = { ok: true };
if (authToken) {
body.token = authToken;
const authInfo = parseAuthInfo(authToken);
if (authInfo) {
const { password, ...rest } = authInfo;
body.auth = rest;
}
}
return NextResponse.json(body);
}
// 生成签名
async function generateSignature(
data: string,
@@ -161,7 +177,7 @@ export async function POST(req: NextRequest) {
// 未配置 PASSWORD 时直接放行
if (!envPassword) {
const response = NextResponse.json({ ok: true });
const response = buildLoginResponse();
// 清除可能存在的认证cookie
response.cookies.set('auth', '', {
@@ -169,7 +185,6 @@ export async function POST(req: NextRequest) {
expires: new Date(0),
sameSite: 'lax',
httpOnly: false,
secure: process.env.NODE_ENV === 'production',
});
return response;
@@ -188,7 +203,6 @@ export async function POST(req: NextRequest) {
}
// 验证成功,设置认证cookie
const response = NextResponse.json({ ok: true });
const username = process.env.USERNAME || 'default';
const deviceInfo = getDeviceInfo(req);
const cookieValue = await generateAuthCookie(
@@ -198,6 +212,7 @@ export async function POST(req: NextRequest) {
true,
deviceInfo
); // localstorage 模式包含 password
const response = buildLoginResponse(cookieValue);
const expires = new Date();
expires.setDate(expires.getDate() + 60); // 60天过期(Refresh Token 有效期)
@@ -255,7 +270,6 @@ export async function POST(req: NextRequest) {
password === process.env.PASSWORD
) {
// 验证成功,设置认证cookie
const response = NextResponse.json({ ok: true });
const deviceInfo = getDeviceInfo(req);
const cookieValue = await generateAuthCookie(
username,
@@ -264,6 +278,7 @@ export async function POST(req: NextRequest) {
false,
deviceInfo
); // 数据库模式不包含 password
const response = buildLoginResponse(cookieValue);
const expires = new Date();
expires.setDate(expires.getDate() + 60); // 60天过期(Refresh Token 有效期)
@@ -308,7 +323,6 @@ export async function POST(req: NextRequest) {
}
// 验证成功,设置认证cookie
const response = NextResponse.json({ ok: true });
const deviceInfo = getDeviceInfo(req);
const cookieValue = await generateAuthCookie(
username,
@@ -317,16 +331,16 @@ export async function POST(req: NextRequest) {
false,
deviceInfo
); // 数据库模式不包含 password
const response = buildLoginResponse(cookieValue);
const expires = new Date();
expires.setDate(expires.getDate() + 60); // 60天过期(Refresh Token 有效期)
response.cookies.set('auth', cookieValue, {
path: '/',
expires,
sameSite: 'lax',
httpOnly: false, // 允许客户端访问
secure: process.env.NODE_ENV === 'production', // 生产环境强制 HTTPS
});
response.cookies.set('auth', cookieValue, {
path: '/',
expires,
sameSite: 'lax',
httpOnly: false, // 允许客户端访问
});
console.log(`Cookie已设置`);
+67 -27
View File
@@ -1,36 +1,85 @@
import { NextRequest } from 'next/server';
// 从cookie获取认证信息 (服务端使用)
export function getAuthInfoFromCookie(request: NextRequest): {
export type AuthInfo = {
password?: string;
username?: string;
signature?: string;
timestamp?: number;
role?: 'owner' | 'admin' | 'user';
} | null {
tokenId?: string;
refreshToken?: string;
refreshExpires?: number;
};
function getAuthTokenFromHeader(value: string): string {
const trimmed = value.trim();
if (!trimmed) {
return '';
}
const bearerMatch = trimmed.match(/^Bearer\s+(.+)$/i);
if (bearerMatch) {
return bearerMatch[1].trim();
}
const tokenMatch = trimmed.match(/^Token\s+(.+)$/i);
if (tokenMatch) {
return tokenMatch[1].trim();
}
return trimmed;
}
export function parseAuthInfo(value?: string | null): AuthInfo | null {
if (!value) {
return null;
}
let decoded = value;
try {
decoded = decodeURIComponent(decoded);
} catch (error) {
decoded = value;
}
if (decoded.includes('%')) {
try {
decoded = decodeURIComponent(decoded);
} catch (error) {
decoded = value;
}
}
try {
return JSON.parse(decoded) as AuthInfo;
} catch (error) {
return null;
}
}
// 从cookie获取认证信息 (服务端使用)
export function getAuthInfoFromCookie(request: NextRequest): AuthInfo | null {
const authHeader = request.headers.get('authorization');
if (authHeader) {
const headerValue = getAuthTokenFromHeader(authHeader);
const headerAuthInfo = parseAuthInfo(headerValue);
if (headerAuthInfo) {
return headerAuthInfo;
}
}
const authCookie = request.cookies.get('auth');
if (!authCookie) {
return null;
}
try {
const decoded = decodeURIComponent(authCookie.value);
const authData = JSON.parse(decoded);
return authData;
} catch (error) {
return null;
}
return parseAuthInfo(authCookie.value);
}
// 从cookie获取认证信息 (客户端使用)
export function getAuthInfoFromBrowserCookie(): {
password?: string;
username?: string;
signature?: string;
timestamp?: number;
role?: 'owner' | 'admin' | 'user';
} | null {
export function getAuthInfoFromBrowserCookie(): AuthInfo | null {
if (typeof window === 'undefined') {
return null;
}
@@ -57,16 +106,7 @@ export function getAuthInfoFromBrowserCookie(): {
return null;
}
// 处理可能的双重编码
let decoded = decodeURIComponent(authCookie);
// 如果解码后仍然包含 %,说明是双重编码,需要再次解码
if (decoded.includes('%')) {
decoded = decodeURIComponent(decoded);
}
const authData = JSON.parse(decoded);
return authData;
return parseAuthInfo(authCookie);
} catch (error) {
return null;
}