70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
/**
|
|
* 客户端认证守卫组件
|
|
*
|
|
* 在客户端检查 localStorage 中的 token
|
|
* 如果未认证且访问的是需要认证的路径,则跳转到登录页
|
|
*/
|
|
|
|
import { useEffect } from 'react';
|
|
import { useNavigate, useLocation } from '@remix-run/react';
|
|
import { isAuthenticated } from '~/utils/auth-storage';
|
|
|
|
interface ClientAuthGuardProps {
|
|
isPublicPath: boolean;
|
|
frontendJWT?: string;
|
|
userInfo?: Record<string, unknown>;
|
|
}
|
|
|
|
export function ClientAuthGuard({ isPublicPath, frontendJWT, userInfo }: ClientAuthGuardProps) {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
|
|
useEffect(() => {
|
|
console.log('🔍 [Auth Guard] useEffect 触发', {
|
|
isPublicPath,
|
|
pathname: location.pathname
|
|
});
|
|
|
|
// 如果是公共路径,不需要检查认证
|
|
if (isPublicPath) {
|
|
console.log('✅ [Auth Guard] 公共路径,跳过认证检查');
|
|
return;
|
|
}
|
|
|
|
// 优先用服务端 session 回传的数据同步 localStorage。
|
|
// 不能只在本地没有 token 时才回填,否则本地残留旧 token 会导致:
|
|
// - SSR 页面可打开(服务端 session 是新的)
|
|
// - CSR 子页面请求 401(客户端 localStorage 还是旧的)
|
|
const token = localStorage.getItem('access_token');
|
|
if (frontendJWT && token !== frontendJWT) {
|
|
localStorage.setItem('access_token', frontendJWT);
|
|
console.log('✅ [Auth Guard] 已根据服务端 session 同步最新 access_token');
|
|
}
|
|
|
|
if (userInfo) {
|
|
const serializedUserInfo = JSON.stringify(userInfo);
|
|
if (localStorage.getItem('user_info') !== serializedUserInfo) {
|
|
localStorage.setItem('user_info', serializedUserInfo);
|
|
console.log('✅ [Auth Guard] 已根据服务端 session 同步最新 user_info');
|
|
}
|
|
}
|
|
|
|
const authenticated = isAuthenticated() || !!frontendJWT;
|
|
|
|
if (!authenticated) {
|
|
console.log('🔒 [Auth Guard] 未认证,重定向到登录页');
|
|
|
|
// 保存当前路径,登录后可以跳转回来
|
|
const redirectTo = location.pathname !== '/login' ? location.pathname : '/';
|
|
|
|
// 跳转到登录页,并传递重定向目标
|
|
navigate(`/login?redirect=${encodeURIComponent(redirectTo)}`, { replace: true });
|
|
} else {
|
|
// console.log('✅ [Auth Guard] 已认证,允许访问');
|
|
}
|
|
}, [isPublicPath, navigate, location.pathname, frontendJWT, userInfo]);
|
|
|
|
// 这个组件不渲染任何内容
|
|
return null;
|
|
}
|