6.8 KiB
6.8 KiB
Session 完整性验证 - 修复说明
🐛 问题描述
现象:用户访问页面时出现错误:
Uncaught TypeError: Cannot read properties of undefined (reading 'nick_name')
at Home (home.tsx:315:33)
根本原因:Session 部分过期或数据丢失时,getUserSession 仍然返回 isAuthenticated: true,但 userInfo 为 undefined。
🔍 问题分析
问题场景
用户登录成功
↓
Cookie Session 创建:
- isAuthenticated: true
- userInfo: { nick_name: "张三", ... }
- accessToken: "..."
- frontendJWT: "..."
↓
经过一段时间(Session 部分失效)
↓
Cookie Session 变为:
- isAuthenticated: true ✅ (仍然保留)
- userInfo: undefined ❌ (已丢失)
- accessToken: undefined ❌ (已丢失)
- frontendJWT: undefined ❌ (已丢失)
↓
getUserSession 返回:
- isAuthenticated: true ❌ (错误!应该是 false)
- userInfo: undefined
↓
home.tsx 访问 userInfo.nick_name
↓
💥 TypeError: Cannot read properties of undefined
原有代码问题
app/api/login/auth.server.ts:340-349 (修复前):
return {
isAuthenticated: isAuthenticated && !isTokenExpired, // ❌ 只检查 token 过期
userRole,
accessToken,
refreshToken,
userInfo, // ❌ 可能是 undefined
isTokenExpired,
refreshedSession,
frontendJWT // ❌ 可能是 undefined
};
问题:
- 只检查
isTokenExpired(需要满足特定条件才会检查) - 不验证
userInfo是否存在 - 不验证
frontendJWT是否存在 - 不验证 OAuth token 信息是否完整
✅ 解决方案
1. 修复 getUserSession - 添加完整性验证
位置:app/api/login/auth.server.ts:340-374
// 🔑 关键:验证 session 完整性
// 如果 isAuthenticated 为 true,但缺少关键数据(userInfo),则认为 session 无效
let finalIsAuthenticated = isAuthenticated && !isTokenExpired;
if (finalIsAuthenticated) {
// 检查是否有用户信息
if (!userInfo) {
console.warn("⚠️ [getUserSession] Session 不完整:缺少 userInfo");
finalIsAuthenticated = false;
}
// 对于非 admin 用户,检查是否有必要的 OAuth token 信息
if (!isAdmin && (!accessToken || !refreshToken)) {
console.warn("⚠️ [getUserSession] Session 不完整:缺少 OAuth token 信息");
finalIsAuthenticated = false;
}
// 检查是否有前端 JWT
if (!frontendJWT) {
console.warn("⚠️ [getUserSession] Session 不完整:缺少 frontendJWT");
finalIsAuthenticated = false;
}
}
return {
isAuthenticated: finalIsAuthenticated, // ✅ 经过完整性验证
userRole,
accessToken,
refreshToken,
userInfo,
isTokenExpired,
refreshedSession,
frontendJWT
};
2. 增强 home.tsx loader - 服务端检查
位置:app/routes/home.tsx:48-85
export async function loader({ request }: LoaderFunctionArgs) {
try {
const { userRole, userInfo, frontendJWT, isAuthenticated } = await getUserSession(request);
// 🔑 检查用户是否已登录且有用户信息
if (!isAuthenticated || !userInfo) {
console.warn("⚠️ [Home Loader] 用户未登录或缺少用户信息,重定向到登录页");
const url = new URL(request.url);
return Response.redirect(`/login?redirect=${encodeURIComponent(url.pathname)}`, 302);
}
return Response.json({
homeData: { /* ... */ },
userRole,
userInfo,
frontendJWT
});
} catch (error) {
console.error('Failed to fetch dashboard data:', error);
return Response.json({ error: '获取数据失败,请稍后重试' }, { status: 500 });
}
}
3. 增强 Home 组件 - 客户端检查
位置:app/routes/home.tsx:111-118
export default function Home() {
const { userInfo, frontendJWT, /* ... */ } = useLoaderData<typeof loader>();
// 🔑 防御性检查:如果 userInfo 不存在,重定向到登录页
if (!userInfo) {
console.error("❌ [Home] userInfo 不存在,重定向到登录页");
if (typeof window !== 'undefined') {
window.location.href = '/login';
}
return null;
}
// ... 正常渲染
}
📊 完整数据流
修复后的流程
用户访问 /home
↓
服务端 loader 执行
↓
调用 getUserSession(request)
↓
检查 session 完整性:
- isAuthenticated: true
- userInfo: undefined ❌
- frontendJWT: undefined ❌
↓
完整性验证失败
↓
返回 { isAuthenticated: false, userInfo: undefined, ... }
↓
home.tsx loader 检查:
if (!isAuthenticated || !userInfo) { ... }
↓
返回 302 重定向
↓
浏览器跳转到 /login?redirect=/home
↓
✅ 用户看到登录页,而不是错误页面
🧪 测试场景
场景 1:Session 部分失效
# 模拟:手动清除 Cookie 中的部分数据(保留 isAuthenticated)
# 预期结果:重定向到登录页
场景 2:Session 完全有效
# 正常登录,访问 /home
# 预期结果:正常显示首页
场景 3:Session 完全失效
# 清除所有 Cookie
# 预期结果:重定向到登录页
⚙️ Session 完整性检查规则
必须存在的数据
对于所有已认证用户:
- ✅
userInfo- 用户信息对象 - ✅
frontendJWT- 前端 JWT token
对于 OAuth 登录用户(非 admin):
- ✅
accessToken- OAuth 访问令牌 - ✅
refreshToken- OAuth 刷新令牌
对于 admin 用户:
- ✅
userInfo- 用户信息对象 - ✅
frontendJWT- 前端 JWT token - ⚠️ OAuth token 可选(使用模拟 token)
验证逻辑
if (isAuthenticated) {
if (!userInfo) → isAuthenticated = false
if (!frontendJWT) → isAuthenticated = false
if (!isAdmin && !accessToken) → isAuthenticated = false
if (!isAdmin && !refreshToken) → isAuthenticated = false
}
🎯 修复收益
- 用户体验改善:Session 失效时看到登录页,而不是错误页面
- 安全性增强:防止部分 session 数据泄露导致的安全问题
- 稳定性提升:防止
undefined访问导致的运行时错误 - 调试友好:清晰的日志输出,便于定位问题
📁 修改的文件
-
app/api/login/auth.server.ts- 添加 session 完整性验证逻辑(line 340-374)
-
app/routes/home.tsx- Loader 添加
isAuthenticated和userInfo检查(line 54-58) - 组件添加客户端防御性检查(line 112-118)
- Loader 添加
-
docs/Session完整性验证_修复说明.md(新增)- 完整的问题分析和修复文档
🔗 相关文档
docs/服务端Token过期处理_实现说明.md- Token 过期处理机制docs/统一登录流程_实施记录.md- 登录流程文档docs/Token自动管理_测试说明.md- Token 管理架构
修复时间: 2025-01-18 修复者: Claude Code