This commit is contained in:
2025-12-05 00:09:32 +08:00
parent bb3d22eabf
commit 3d1dbb3f97
214 changed files with 113060 additions and 1232 deletions
+268
View File
@@ -0,0 +1,268 @@
# 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<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
✅ 用户看到登录页,而不是错误页面
```
## 🧪 测试场景
### 场景 1Session 部分失效
```bash
# 模拟:手动清除 Cookie 中的部分数据(保留 isAuthenticated
# 预期结果:重定向到登录页
```
### 场景 2Session 完全有效
```bash
# 正常登录,访问 /home
# 预期结果:正常显示首页
```
### 场景 3Session 完全失效
```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