# 前端认证管理优化

## 问题描述

之前前端的认证管理存在以下问题：

1. **401 错误处理不完整**：只在响应拦截器的错误处理中处理 HTTP 401，但如果后端返回 `{success: false, code: 401}` 的业务错误（HTTP 200），不会触发跳转登录页
2. **业务错误码未检查认证失败**：`handleBusinessError` 函数没有检查认证相关的业务错误码（401, 40101, 40102, 40103）
3. **Token 刷新可能失败但没有统一处理**：某些接口可能返回 401 但没有正确跳转登录页
4. **缺少 Token 自动刷新机制**：Token 过期前没有自动刷新，导致用户操作时突然失效

## 优化方案

### 1. 统一认证错误处理

#### 1.1 响应拦截器优化

**文件**: `frontend/src/api/request.ts`

**优化内容**：
- 在成功响应中检查业务错误码（401, 40101, 40102, 40103）
- 优先处理认证错误，不依赖 `skipErrorHandler` 配置
- 统一跳转登录页逻辑

```typescript
// 响应拦截器 - 成功响应处理
instance.interceptors.response.use(
  (response: AxiosResponse) => {
    // 检查业务状态码
    const data = response.data as ApiResponse
    if (data && typeof data === 'object' && 'success' in data) {
      if (!data.success) {
        // 检查是否是认证错误（优先处理，不依赖 skipErrorHandler）
        const code = data.code
        if (code === 401 || code === 40101 || code === 40102 || code === 40103) {
          console.log('🔒 业务错误：认证失败 (HTTP 200)，跳转登录页')
          authStore.clearAuthInfo()
          router.push('/login')
          ElMessage.error(data.message || '登录已过期，请重新登录')
          return Promise.reject(new Error(data.message || '认证失败'))
        }
        
        // 其他业务错误
        if (!config.skipErrorHandler) {
          handleBusinessError(data)
          return Promise.reject(new Error(data.message || '请求失败'))
        }
      }
    }

    return response.data
  },
  // ... 错误响应处理
)
```

#### 1.2 业务错误处理优化

**文件**: `frontend/src/api/request.ts`

**优化内容**：
- 在 `handleBusinessError` 函数中添加认证错误码处理
- 统一认证错误的处理逻辑

```typescript
const handleBusinessError = (data: ApiResponse) => {
  const { code, message } = data
  const authStore = useAuthStore()

  switch (code) {
    case 401:
    case 40101:  // 未授权
    case 40102:  // Token 无效
    case 40103:  // Token 过期
      console.log('🔒 业务错误：认证失败，跳转登录页')
      authStore.clearAuthInfo()
      router.push('/login')
      ElMessage.error(message || '登录已过期，请重新登录')
      break
    // ... 其他错误码处理
  }
}
```

### 2. 全局错误处理

**文件**: `frontend/src/main.ts`

**优化内容**：
- 在全局错误处理器中捕获未处理的认证错误
- 确保所有认证错误都能跳转到登录页

```typescript
app.config.errorHandler = (err, vm, info) => {
  console.error('全局错误:', err, info)
  
  // 检查是否是认证错误
  if (err && typeof err === 'object') {
    const error = err as any
    if (
      error.message?.includes('认证失败') ||
      error.message?.includes('登录已过期') ||
      error.message?.includes('Token') ||
      error.response?.status === 401 ||
      error.code === 401
    ) {
      console.log('🔒 全局错误处理：检测到认证错误，跳转登录页')
      const authStore = useAuthStore()
      authStore.clearAuthInfo()
      router.push('/login')
    }
  }
}
```

### 3. Token 自动刷新机制

**文件**: `frontend/src/utils/auth.ts`

**新增功能**：
- `isTokenValid()`: 检查 token 是否有效
- `parseToken()`: 解析 token 获取 payload
- `getTokenRemainingTime()`: 获取 token 剩余有效时间
- `isTokenExpiringSoon()`: 检查 token 是否即将过期（默认 5 分钟）
- `autoRefreshToken()`: 自动刷新即将过期的 token
- `setupTokenRefreshTimer()`: 设置定时器，每分钟检查并刷新 token

**使用方式**：

```typescript
// 在应用初始化时启动定时器
import { setupTokenRefreshTimer } from '@/utils/auth'

// 用户登录成功后
if (authStore.isAuthenticated) {
  setupTokenRefreshTimer()
}
```

### 4. 认证工具函数

**文件**: `frontend/src/utils/auth.ts`

**新增功能**：
- `isAuthError()`: 检查是否是认证错误
- `handleAuthError()`: 统一处理认证错误
- Token 相关工具函数

**使用示例**：

```typescript
import { isAuthError, handleAuthError } from '@/utils/auth'

try {
  const response = await api.getData()
} catch (error) {
  if (isAuthError(error)) {
    handleAuthError(error)
  } else {
    // 处理其他错误
  }
}
```

## 认证错误码规范

### HTTP 状态码
- `401`: 未授权（Unauthorized）

### 业务错误码
- `401`: 认证失败（通用）
- `40101`: 未授权（未登录）
- `40102`: Token 无效
- `40103`: Token 过期

## 认证流程

### 1. 登录流程

```
用户输入账号密码
    ↓
调用登录 API
    ↓
后端验证成功，返回 access_token 和 refresh_token
    ↓
前端保存 token 到 localStorage 和 Pinia store
    ↓
设置 axios 请求头 Authorization
    ↓
启动 token 自动刷新定时器
    ↓
跳转到目标页面
```

### 2. Token 刷新流程

```
定时器每分钟检查 token
    ↓
检查 token 是否即将过期（< 5 分钟）
    ↓
如果即将过期，调用刷新 API
    ↓
使用 refresh_token 获取新的 access_token
    ↓
更新 localStorage 和 Pinia store
    ↓
更新 axios 请求头
```

### 3. 认证失败处理流程

```
API 请求返回 401 或认证错误码
    ↓
响应拦截器捕获错误
    ↓
检查是否是 refresh 请求本身失败
    ↓
如果不是，尝试刷新 token
    ↓
如果刷新成功，重试原请求
    ↓
如果刷新失败，清除认证信息
    ↓
跳转到登录页
    ↓
显示错误提示
```

## 测试场景

### 1. Token 过期测试

**场景**: 用户长时间未操作，token 过期

**预期行为**:
1. 用户操作时，API 返回 401
2. 前端自动尝试刷新 token
3. 如果刷新成功，继续原操作
4. 如果刷新失败，跳转到登录页

### 2. Refresh Token 过期测试

**场景**: refresh_token 也过期了

**预期行为**:
1. 用户操作时，API 返回 401
2. 前端尝试刷新 token，但 refresh_token 也无效
3. 清除认证信息
4. 跳转到登录页
5. 显示"登录已过期，请重新登录"

### 3. 业务错误码测试

**场景**: 后端返回 HTTP 200，但 `{success: false, code: 401}`

**预期行为**:
1. 响应拦截器检测到业务错误码 401
2. 清除认证信息
3. 跳转到登录页
4. 显示错误消息

### 4. Token 自动刷新测试

**场景**: Token 即将过期（剩余 < 5 分钟）

**预期行为**:
1. 定时器检测到 token 即将过期
2. 自动调用刷新 API
3. 更新 token
4. 用户无感知，继续操作

## 注意事项

1. **避免无限循环**: 如果 refresh 请求本身返回 401，不要再次尝试刷新
2. **网络错误处理**: 网络错误或服务器错误（5xx）不要清除认证信息
3. **并发请求**: 多个请求同时返回 401 时，只刷新一次 token
4. **路由守卫**: 确保路由守卫和 API 拦截器的认证逻辑一致
5. **Token 存储**: 同时使用 localStorage 和 Pinia store，确保刷新页面后认证状态不丢失

## 相关文件

- `frontend/src/api/request.ts`: API 请求拦截器和响应拦截器
- `frontend/src/stores/auth.ts`: 认证状态管理
- `frontend/src/router/index.ts`: 路由守卫
- `frontend/src/main.ts`: 应用初始化和全局错误处理
- `frontend/src/utils/auth.ts`: 认证工具函数
- `frontend/src/views/Auth/Login.vue`: 登录页面

## 后续优化建议

1. **Token 刷新队列**: 实现请求队列，避免并发刷新
2. **离线模式**: 支持离线缓存，网络恢复后自动同步
3. **多标签页同步**: 使用 BroadcastChannel 或 localStorage 事件同步多标签页的认证状态
4. **安全增强**: 实现 CSRF 保护、XSS 防护等安全措施
5. **监控和日志**: 集成前端监控服务，记录认证相关的错误和异常

