生产管理系统 - 未登录拦截 客户端中间件开发
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { AuthProvider } from '@/components/auth/AuthContext';
|
||||
import { ClientAuthInterceptor } from '@/components/auth/ClientAuthInterceptor';
|
||||
|
||||
export default function AppLayout({
|
||||
children,
|
||||
@@ -14,10 +15,12 @@ export default function AppLayout({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<html lang="zh-CN" suppressHydrationWarning>
|
||||
<body suppressHydrationWarning>
|
||||
<AuthProvider>
|
||||
<ClientAuthInterceptor>
|
||||
{children}
|
||||
</ClientAuthInterceptor>
|
||||
</AuthProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
75
crop-x/src/components/auth/ClientAuthInterceptor.tsx
Normal file
75
crop-x/src/components/auth/ClientAuthInterceptor.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuth } from './AuthContext';
|
||||
|
||||
interface ClientAuthInterceptorProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function ClientAuthInterceptor({ children }: ClientAuthInterceptorProps) {
|
||||
const { user, isAuthenticated, loading } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
// 如果正在加载,等待加载完成
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentPath = window.location.pathname;
|
||||
|
||||
// 如果已经在登录页面,不需要重定向
|
||||
if (currentPath === '/login' || currentPath === '/register') {
|
||||
console.log(`📄 已在认证页面,跳过拦截: ${currentPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果未认证,重定向到登录页
|
||||
if (!isAuthenticated) {
|
||||
console.log(`🔒 客户端拦截:未认证用户访问 ${currentPath},重定向到登录页`);
|
||||
|
||||
// 保存当前路径,登录后可以跳转回来
|
||||
const loginUrl = `/login${currentPath !== '/' ? `?redirect=${encodeURIComponent(currentPath)}` : ''}`;
|
||||
router.push(loginUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ 客户端认证通过:用户 ${user?.username} 访问 ${window.location.pathname}`);
|
||||
}, [isAuthenticated, loading, user, router]);
|
||||
|
||||
const currentPath = typeof window !== 'undefined' ? window.location.pathname : '';
|
||||
|
||||
// 如果正在加载,显示加载状态
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="text-center">
|
||||
<div className="w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full animate-spin mb-4"></div>
|
||||
<p className="text-gray-600">验证用户身份中...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 如果在认证页面(登录/注册),直接渲染子组件,不需要认证检查
|
||||
if (currentPath === '/login' || currentPath === '/register') {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
// 如果未认证且不在认证页面,显示跳转状态
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="text-center">
|
||||
<div className="w-8 h-8 border-2 border-orange-500 border-t-transparent rounded-full animate-spin mb-4"></div>
|
||||
<p className="text-gray-600">正在跳转到登录页...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 认证通过,渲染子组件
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
// 定义不需要认证的路径
|
||||
const publicPaths = [
|
||||
"/login",
|
||||
"/register",
|
||||
"/api",
|
||||
"/_next",
|
||||
"/favicon.ico",
|
||||
"/static"
|
||||
];
|
||||
|
||||
export async function middleware(request: any) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
console.log(`🔍 中间件拦截路径: ${pathname}`);
|
||||
|
||||
// 检查是否是公开路径
|
||||
if (publicPaths.some(path => pathname.startsWith(path))) {
|
||||
console.log(`✅ 公开路径,允许访问: ${pathname}`);
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
try {
|
||||
// 从 cookie 中获取 token
|
||||
const token = request.cookies.get("auth-token")?.value;
|
||||
|
||||
console.log(`🍪 Cookie中的token状态: ${token ? '存在' : '不存在'}`);
|
||||
|
||||
// 如果没有 token,重定向到登录页
|
||||
if (!token) {
|
||||
console.log(`🔒 未找到认证 token,重定向到登录页: ${pathname}`);
|
||||
const loginUrl = new URL("/login", request.url);
|
||||
loginUrl.searchParams.set("redirect", pathname);
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
|
||||
// 简单验证 token 格式(不调用API,避免复杂依赖)
|
||||
if (token.length < 10) {
|
||||
console.log(`❌ Token 格式无效,重定向到登录页: ${pathname}`);
|
||||
const loginUrl = new URL("/login", request.url);
|
||||
loginUrl.searchParams.set("redirect", pathname);
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
|
||||
console.log(`✅ Token 存在,允许访问: ${pathname}`);
|
||||
|
||||
// 创建响应并添加用户信息标记
|
||||
const response = NextResponse.next();
|
||||
response.headers.set('x-middleware-auth', 'validated');
|
||||
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 中间件处理失败,重定向到登录页: ${pathname}`, error);
|
||||
// 发生错误时,重定向到登录页
|
||||
const loginUrl = new URL("/login", request.url);
|
||||
loginUrl.searchParams.set("redirect", pathname);
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* 匹配所有路径除了:
|
||||
* - api (API routes)
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
* - login 和 register 页面
|
||||
*/
|
||||
"/((?!api|_next/static|_next/image|favicon.ico|login|register).*)",
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user