# 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` (修复前)**: ```typescript return { isAuthenticated: isAuthenticated && !isTokenExpired, // ❌ 只检查 token 过期 userRole, accessToken, refreshToken, userInfo, // ❌ 可能是 undefined isTokenExpired, refreshedSession, frontendJWT // ❌ 可能是 undefined }; ``` **问题**: 1. 只检查 `isTokenExpired`(需要满足特定条件才会检查) 2. 不验证 `userInfo` 是否存在 3. 不验证 `frontendJWT` 是否存在 4. 不验证 OAuth token 信息是否完整 ## ✅ 解决方案 ### 1. 修复 `getUserSession` - 添加完整性验证 **位置**:`app/api/login/auth.server.ts:340-374` ```typescript // 🔑 关键:验证 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` ```typescript 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` ```typescript export default function Home() { const { userInfo, frontendJWT, /* ... */ } = useLoaderData(); // 🔑 防御性检查:如果 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 部分失效 ```bash # 模拟:手动清除 Cookie 中的部分数据(保留 isAuthenticated) # 预期结果:重定向到登录页 ``` ### 场景 2:Session 完全有效 ```bash # 正常登录,访问 /home # 预期结果:正常显示首页 ``` ### 场景 3:Session 完全失效 ```bash # 清除所有 Cookie # 预期结果:重定向到登录页 ``` ## ⚙️ Session 完整性检查规则 ### 必须存在的数据 对于所有已认证用户: - ✅ `userInfo` - 用户信息对象 - ✅ `frontendJWT` - 前端 JWT token 对于 OAuth 登录用户(非 admin): - ✅ `accessToken` - OAuth 访问令牌 - ✅ `refreshToken` - OAuth 刷新令牌 对于 admin 用户: - ✅ `userInfo` - 用户信息对象 - ✅ `frontendJWT` - 前端 JWT token - ⚠️ OAuth token 可选(使用模拟 token) ### 验证逻辑 ```typescript if (isAuthenticated) { if (!userInfo) → isAuthenticated = false if (!frontendJWT) → isAuthenticated = false if (!isAdmin && !accessToken) → isAuthenticated = false if (!isAdmin && !refreshToken) → isAuthenticated = false } ``` ## 🎯 修复收益 1. **用户体验改善**:Session 失效时看到登录页,而不是错误页面 2. **安全性增强**:防止部分 session 数据泄露导致的安全问题 3. **稳定性提升**:防止 `undefined` 访问导致的运行时错误 4. **调试友好**:清晰的日志输出,便于定位问题 ## 📁 修改的文件 1. **`app/api/login/auth.server.ts`** - 添加 session 完整性验证逻辑(line 340-374) 2. **`app/routes/home.tsx`** - Loader 添加 `isAuthenticated` 和 `userInfo` 检查(line 54-58) - 组件添加客户端防御性检查(line 112-118) 3. **`docs/Session完整性验证_修复说明.md`**(新增) - 完整的问题分析和修复文档 ## 🔗 相关文档 - `docs/服务端Token过期处理_实现说明.md` - Token 过期处理机制 - `docs/统一登录流程_实施记录.md` - 登录流程文档 - `docs/Token自动管理_测试说明.md` - Token 管理架构 --- **修复时间**: 2025-01-18 **修复者**: Claude Code