Merge branch 'dev'
This commit is contained in:
@@ -72,24 +72,8 @@ export async function POST(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const accessTokenAge = now - authInfo.timestamp;
|
|
||||||
const remainingAccessTime = TOKEN_CONFIG.ACCESS_TOKEN_AGE - accessTokenAge;
|
|
||||||
const refreshWindow = 15 * 60 * 1000;
|
|
||||||
|
|
||||||
if (remainingAccessTime <= 0) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Access token expired' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainingAccessTime > refreshWindow) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Refresh not allowed' },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 只检查 Refresh Token 是否过期
|
||||||
if (now >= authInfo.refreshExpires) {
|
if (now >= authInfo.refreshExpires) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Refresh token expired' },
|
{ error: 'Refresh token expired' },
|
||||||
@@ -97,6 +81,8 @@ export async function POST(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 只要 Refresh Token 有效,就允许刷新(即使 Access Token 已过期)
|
||||||
|
|
||||||
const newAuthData = await refreshAccessToken(
|
const newAuthData = await refreshAccessToken(
|
||||||
authInfo.username,
|
authInfo.username,
|
||||||
authInfo.role,
|
authInfo.role,
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ export function TokenRefreshManager() {
|
|||||||
const age = now - authInfo.timestamp;
|
const age = now - authInfo.timestamp;
|
||||||
const remaining = ACCESS_TOKEN_AGE - age;
|
const remaining = ACCESS_TOKEN_AGE - age;
|
||||||
|
|
||||||
// 剩余时间 < 10 分钟时需要刷新
|
// 剩余时间 < 10 分钟时需要刷新(包括已过期的情况)
|
||||||
const REFRESH_THRESHOLD = 10 * 60 * 1000; // 10 分钟
|
const REFRESH_THRESHOLD = 10 * 60 * 1000; // 10 分钟
|
||||||
return remaining < REFRESH_THRESHOLD && remaining > 0;
|
return remaining < REFRESH_THRESHOLD;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 保存原始 fetch
|
// 保存原始 fetch
|
||||||
@@ -132,7 +132,7 @@ export function TokenRefreshManager() {
|
|||||||
// 刷新成功,重试原请求(仅此一次)
|
// 刷新成功,重试原请求(仅此一次)
|
||||||
response = await originalFetch(input, init);
|
response = await originalFetch(input, init);
|
||||||
|
|
||||||
// 如果重试后仍然是 401,说明有其他问题,不再重试
|
// 如果重试后仍然是 401,说明有问题,跳转登录
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
console.error('[Token] Still 401 after refresh, redirecting to login');
|
console.error('[Token] Still 401 after refresh, redirecting to login');
|
||||||
window.location.href = `/login?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`;
|
window.location.href = `/login?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`;
|
||||||
|
|||||||
+23
-3
@@ -521,9 +521,25 @@ async function fetchWithAuth(
|
|||||||
url: string,
|
url: string,
|
||||||
options?: RequestInit
|
options?: RequestInit
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const res = await fetch(url, options);
|
let res = await fetch(url, options);
|
||||||
if (!res.ok) {
|
|
||||||
// 如果是 401 未授权,跳转到登录页面
|
// 如果是 401 且是 token 过期,尝试刷新并重试
|
||||||
|
if (res.status === 401) {
|
||||||
|
const text = await res.clone().text();
|
||||||
|
if (text === 'Access token expired') {
|
||||||
|
// 尝试刷新 token
|
||||||
|
const refreshRes = await fetch('/api/auth/refresh', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (refreshRes.ok) {
|
||||||
|
// 刷新成功,重试原请求
|
||||||
|
res = await fetch(url, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果刷新后仍然是 401,或者是其他 401 错误,跳转登录
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
// 调用 logout 接口
|
// 调用 logout 接口
|
||||||
try {
|
try {
|
||||||
@@ -540,8 +556,12 @@ async function fetchWithAuth(
|
|||||||
window.location.href = loginUrl.toString();
|
window.location.href = loginUrl.toString();
|
||||||
throw new Error('用户未授权,已跳转到登录页面');
|
throw new Error('用户未授权,已跳转到登录页面');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
throw new Error(`请求 ${url} 失败: ${res.status}`);
|
throw new Error(`请求 ${url} 失败: ${res.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-4
@@ -48,17 +48,29 @@ export async function middleware(request: NextRequest) {
|
|||||||
return handleAuthFailure(request, pathname);
|
return handleAuthFailure(request, pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证 Access Token 时间戳
|
// 验证 Token 时间戳
|
||||||
const ACCESS_TOKEN_AGE = TOKEN_CONFIG.ACCESS_TOKEN_AGE;
|
const ACCESS_TOKEN_AGE = TOKEN_CONFIG.ACCESS_TOKEN_AGE;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const age = now - authInfo.timestamp;
|
const age = now - authInfo.timestamp;
|
||||||
|
|
||||||
// Access Token 已过期,前端负责刷新
|
// 先检查 Refresh Token 是否过期
|
||||||
if (age > ACCESS_TOKEN_AGE) {
|
if (now >= authInfo.refreshExpires) {
|
||||||
console.log(`Access token expired for ${authInfo.username}, redirecting to login`);
|
console.log(`Refresh token expired for ${authInfo.username}, redirecting to login`);
|
||||||
return handleAuthFailure(request, pathname);
|
return handleAuthFailure(request, pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Access Token 已过期
|
||||||
|
if (age > ACCESS_TOKEN_AGE) {
|
||||||
|
console.log(`Access token expired for ${authInfo.username}`);
|
||||||
|
// 对于 API 请求,返回 401,让前端拦截器刷新并重试
|
||||||
|
if (pathname.startsWith('/api')) {
|
||||||
|
return new NextResponse('Access token expired', { status: 401 });
|
||||||
|
}
|
||||||
|
// 对于页面请求,允许通过,让前端 TokenRefreshManager 在页面加载后刷新
|
||||||
|
// 不能返回 401 或重定向,否则页面无法加载,前端代码无法运行
|
||||||
|
console.log(`Allowing page request to pass, frontend will refresh token`);
|
||||||
|
}
|
||||||
|
|
||||||
// Access Token 未过期,验证签名
|
// Access Token 未过期,验证签名
|
||||||
const isValidSignature = await verifySignature(
|
const isValidSignature = await verifySignature(
|
||||||
authInfo.username,
|
authInfo.username,
|
||||||
|
|||||||
Reference in New Issue
Block a user