Files
ephron-ren-prd/bugs/login-redirect-csp-bug.md

240 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ephron.ren 登录重定向失败问题分析报告
> **严重等级**: 🔴 Critical
> **影响范围**: 所有需要登录的页面Home / Blog / Canvas / Prompt / Auth
> **发现日期**: 2026-05-05
> **状态**: 已确认复现
---
## 1. 问题概述
在 ephron.ren 全站所有页面中,未登录用户点击「登录」后跳转至 `auth.ephron.ren/login?redirect=<base64编码的目标URL>`,填写账号密码点击登录按钮后,**无法重定向回原始页面**,始终停留在登录页。
**影响的所有入口**:
| 来源页面 | 登录页 redirect 参数 | 结果 |
|----------|---------------------|------|
| https://www.ephron.ren/ | `aHR0cHM6Ly93d3cuZXBocm9uLnJlbi8=` | ❌ 失败 |
| https://blog.ephron.ren/ | `aHR0cHM6Ly9ibG9nLmVwaHJvbi5yZW4v` | ❌ 失败 |
| https://canvas.ephron.ren/ | `aHR0cHM6Ly9jYW52YXMuZXBocm9uLnJlbi8=` | ❌ 失败 |
| https://prompt.ephron.ren/ | `aHR0cHM6Ly9wcm9tcHQuZXBocm9uLnJlbi8=` | ❌ 失败 |
| auth.ephron.ren/admin同源 | `aHR0cHM6Ly9hdXRoLmVwaHJvbi5yZW4vYWRtaW4=` | ✅ 成功 |
| 无 redirect 参数 | N/A → `/login-success` | ✅ 成功 |
**关键发现**: 当 redirect 目标与 auth.ephron.ren **同源**时登录正常,**跨源**(不同子域)时失败。
---
## 2. 根因分析
### 2.1 问题根因
**CSP `form-action 'self'` 策略阻止了登录接口的 303 跨源重定向。**
完整流程分析:
```
① 用户在 www.ephron.ren 点击「登录」
→ 跳转到 https://auth.ephron.ren/login?redirect=aHR0cHM6Ly93d3cuZXBocm9uLnJlbi8=
② 用户填写账号密码,点击「登录」按钮
→ 浏览器 POST 到 https://auth.ephron.ren/api/login同源 ✅ 允许)
③ 服务端验证成功,返回 HTTP 303 重定向
→ Location: https://www.ephron.ren/(跨源 ❌)
→ 响应头包含 Content-Security-Policy: ... form-action 'self'
④ 浏览器检查 CSP form-action 策略
→ 重定向目标 https://www.ephron.ren/ ≠ 'self'https://auth.ephron.ren
→ 浏览器阻止重定向,停留在登录页
⑤ 结果Cookie 已设置ephron_auth但页面未跳转
```
### 2.2 技术细节
#### CSP 配置auth.ephron.ren 所有响应)
```
Content-Security-Policy: default-src 'self';
script-src 'self' 'unsafe-inline';
script-src-elem 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline';
style-src-elem 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net https://maxcdn.bootstrapcdn.com;
img-src 'self' data: https:;
font-src 'self' data: https://fonts.gstatic.com https:;
connect-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
```
关键指令: **`form-action 'self'`**
#### CSP `form-action` 行为说明
根据 CSP Level 2/3 规范,`form-action` 指令限制的是「表单提交的目标 URL」。在 Chromium 的实现中,表单提交触发的 **整个重定向链** 都受此策略约束:
1. 表单 POST 到 `/api/login``https://auth.ephron.ren`)→ ✅ 同源,允许
2. 服务端返回 303 到 `https://www.ephron.ren/` → ❌ 跨源,被阻止
#### 登录接口响应curl 验证)
```http
HTTP/2 303
location: https://www.ephron.ren/
set-cookie: ephron_auth=eyJ2...; Domain=.ephron.ren; HttpOnly; Max-Age=604800; Path=/; SameSite=lax; Secure
content-security-policy: ... form-action 'self'
```
服务端逻辑完全正确:验证凭证 → 签发 token → 设置 Cookie → 303 重定向。但浏览器因 CSP 策略拒绝执行重定向。
#### 源码定位
- **CSP 配置**: `shared/security_headers.py` 第 8-20 行,`_CSP_POLICY` 常量
- **登录接口**: `auth/src/routes/api.py` 第 174-241 行,`POST /api/login`
- **重定向校验**: `auth/src/utils/redirect.py` 第 16-75 行,`validate_redirect()`
- **登录页面模板**: `auth/templates/login.html` 第 140 行,表单 action
---
## 3. 浏览器复现证据
### 3.1 控制台错误
```
[ERROR] Sending form data to 'https://auth.ephron.ren/api/login' violates
the following Content Security Policy directive: "form-action 'self'".
```
### 3.2 网络请求分析
| 请求 | 状态 | 说明 |
|------|------|------|
| `GET /login?redirect=...` | 200 ✅ | 登录页正常加载 |
| `POST /api/login` | 已发送 ✅ | 浏览器确实发出了 POST 请求 |
| 303 重定向响应 | ❌ 未跟随 | 浏览器收到 303 但未执行跳转 |
### 3.3 Cookie 状态
登录后 `ephron_auth` Cookie 已正确设置(`Domain=.ephron.ren; HttpOnly; Secure; SameSite=Lax`),说明服务端逻辑完全正常。问题纯粹在客户端 CSP 策略。
### 3.4 对照实验
| 测试场景 | 结果 | CSP 错误 |
|----------|------|----------|
| 无 redirect 参数登录 | ✅ 跳转到 `/login-success` | 无 |
| redirect = `auth.ephron.ren/admin`(同源) | ✅ 跳转到 admin | 无 |
| redirect = `www.ephron.ren`(跨源) | ❌ 停留登录页 | `form-action 'self'` |
| redirect = `blog.ephron.ren`(跨源) | ❌ 停留 API URL | `form-action 'self'` |
| redirect = `canvas.ephron.ren`(跨源) | ❌ 停留 API URL | `form-action 'self'` |
| redirect = `prompt.ephron.ren`(跨源) | ❌ 停留 API URL | `form-action 'self'` |
---
## 4. 修复方案
### 方案 A推荐: 从 303 响应中移除 CSP 头
`shared/security_headers.py` 的中间件中,对 303 重定向响应不添加 CSP 头:
```python
@app.middleware("http")
async def _security_headers(request: Request, call_next):
response = await call_next(request)
response.headers.setdefault("X-Content-Type-Options", "nosniff")
response.headers.setdefault("X-Frame-Options", "DENY")
response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
# 仅对非重定向响应添加 CSP避免 form-action 阻止跨源重定向)
if response.status_code not in (301, 302, 303, 307, 308):
response.headers.setdefault("Content-Security-Policy", _CSP_POLICY)
# ... Cache-Control 逻辑不变
```
**优点**: 最小改动,不影响其他页面的 CSP 保护
**缺点**: 重定向响应失去 CSP 保护(但重定向响应通常无 HTML 内容CSP 保护意义不大)
### 方案 B: 修改 form-action 策略
`form-action 'self'` 改为允许所有 ephron.ren 子域:
```
form-action 'self' https://*.ephron.ren
```
或更精确地列出所有子域:
```
form-action 'self' https://www.ephron.ren https://blog.ephron.ren https://canvas.ephron.ren https://prompt.ephron.ren
```
**优点**: 明确允许列表,安全性可审计
**缺点**: 新增子域需同步更新 CSP通配符 `*` 可能过于宽松
### 方案 C: 改用 JavaScript 重定向
修改登录接口,返回 200 + JSON由前端 JS 执行 `window.location.href` 跳转:
```javascript
// 前端
const res = await fetch('/api/login', { method: 'POST', body: formData });
const data = await res.json();
if (data.success) {
window.location.href = data.redirect_url;
}
```
**优点**: 完全绕开 CSP form-action 限制
**缺点**: 需要修改前后端逻辑JS 禁用时无法登录(渐进增强降级)
### 方案 D: 使用中间页面中转
登录成功后先重定向到 `auth.ephron.ren/redirect?url=<target>`(同源),再由该页面通过 meta refresh 或 JS 跳转到目标:
```html
<!-- auth.ephron.ren/redirect 页面 -->
<meta http-equiv="refresh" content="0;url=https://www.ephron.ren/">
<script>window.location.href = decodeURIComponent(params.get('url'));</script>
```
**优点**: 保持 form-action 'self' 不变
**缺点**: 多一次跳转,增加延迟;需要额外的路由和页面
---
## 5. 推荐修复路径
**推荐方案 A**,理由:
1. **改动最小**: 仅修改 `security_headers.py` 中间件的 1 行逻辑
2. **安全性可接受**: 303 重定向响应体为空(`content-length: 0`CSP 对其无实际保护作用
3. **不影响其他安全头**: `X-Content-Type-Options``X-Frame-Options``Referrer-Policy` 仍正常添加
4. **无需修改前端**: 登录流程保持 HTML 表单提交,不依赖 JavaScript
---
## 6. 附录
### 6.1 受影响的完整页面列表
所有跳转到 `auth.ephron.ren/login?redirect=...` 的页面均受影响:
- `www.ephron.ren` — 首页
- `blog.ephron.ren` — 博客(含文章详情页、管理后台)
- `canvas.ephron.ren` — 画布
- `prompt.ephron.ren` — 提示词(含详情页、管理后台)
- `auth.ephron.ren` — 注册成功后跳转、管理后台
### 6.2 注册表单同样受影响
注册页面 (`auth.ephron.ren/register`) 使用相同的 CSP 策略和表单提交模式,注册成功后的 303 重定向也可能受 `form-action 'self'` 影响。需一并验证和修复。
### 6.3 测试环境
- 浏览器: Chromium (Playwright headless)
- 测试账号: `Elaina_user` / `Elaina_owner`
- 测试时间: 2026-05-05 22:00-22:30 CST