Files
nanocode/usercenter/docs/sso-plan.md
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

131 lines
5.4 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.
# 单台服务器多端口 SSO 方案
## 场景约束
- 单台服务器,公网 IP `129.211.173.87`,无域名
- 所有系统通过 `IP:端口` 访问
- 已安装 Redis,不需要 HTTPS(纯 HTTP)
- 目标项目:`personal-blog`(FastAPI 后端 + React 前端)
## 核心原理
同 IP 下不同端口属于 **same-site**,浏览器会自动携带该 IP 下的所有 Cookie。
因此:
- SSO 中心登录后种下 Cookie(Domain 设为 IP 地址)
- 各业务系统同 IP 不同端口均可读取该 Cookie
- 无需 OAuth2 跳转,无需重定向
> 不同端口之间发 AJAX 仍属于 **cross-origin**,需要 CORS 配置 `allow_credentials=True`。
## 架构
```
┌─────────────────────────────────────────────────────────┐
│ 129.211.173.87 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 业务前端 │ │ 业务后端 │ │ SSO中心 │ │
│ │ :5173 │ │ :8889 │ │ :9000 │ │
│ │ React+Vite │ │ FastAPI │ │ FastAPI │ │
│ │ axios │ │ 验签Cookie │ │ 签发JWT │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └──────────────────┴──────────────────┘ │
│ 共享 IP 的 Cookie │
└─────────────────────────────────────────────────────────┘
```
## 数据流
### 登录
```
1. 用户访问 blog 前端 (:5173),未登录,跳转 SSO 登录页 (:9000)
2. SSO 登录页提交用户名密码到 :9000/api/login
3. 验证通过后,SSO 种下两个 Cookie:
- access_token (JWT, 15分钟)
- refresh_token (随机串, Redis 存 7天)
4. 跳转回 blog 前端 (:5173)
5. blog 前端 axios 请求 blog 后端,自动携带 Cookie
6. blog 后端从 Cookie 读 access_token,本地验签,返回数据
```
### 登出
```
1. 前端调用 :9000/api/logout
2. SSO 清除 Cookie,Redis 删除 refresh_token
3. 业务系统再请求时 Cookie 已空,返回 401
```
## Cookie 设计
| 属性 | 值 | 说明 |
|------|-----|------|
| Name | `access_token` / `refresh_token` | |
| Domain | `129.211.173.87` | 同 IP 共享 |
| Path | `/` | 全站可用 |
| HttpOnly | `true` | 防 XSS |
| SameSite | `Lax` | 同 IP 不同端口算 same-site,Lax 足够;HTTP 下不能用 None+Secure |
| MaxAge | 900s / 604800s | 15分钟 / 7天 |
## SSO 中心实现清单
- `app.py` — FastAPI 主应用,端口 9000
- `database.py` — SQLite 用户库
- `models.py` — User 模型
- `sso_config.py` — SECRET_KEY / Redis 配置
- `static/login.html` — 独立登录页
- `requirements.txt`
接口:
- `POST /api/login` — 登录,种 Cookie
- `POST /api/logout` — 登出,清 Cookie
- `GET /api/userinfo` — 读 Cookie 返回当前用户
- `GET /api/validate` — 读 Cookie 返回验证结果(供业务系统可选调用)
- `GET /` — 返回登录页
## 业务系统接入修改(personal-blog)
**原则:改最小,不动原有登录**
### 后端(`backend/`)
1. **新增 `sso_client.py`**
- 提供 `get_current_user_from_cookie()` 函数
- 从请求 Cookie 读 `access_token`
- 用 SSO 相同的 SECRET_KEY 本地验签(HS256)
- 返回 `{"username": "..."}`
2. **修改 `routers/auth.py`**
- `get_current_admin` 改为:
- 先尝试原有 `OAuth2PasswordBearer` Header(兼容旧本地登录)
- Header 不存在则尝试 Cookie 中的 SSO token
- SSO token 验签通过后,用 username 查本地 Admin 表
- 查到则返回,查不到则 401
3. **修改 `app.py` CORS**
- `allow_origins` 加入 `http://129.211.173.87:9000`(SSO中心)
- `allow_credentials=True` 保持开启
### 前端(`frontend/src/`)
1. **修改 `api/config.js`**
- axios 增加 `withCredentials: true`(让跨域请求携带 Cookie)
- CORS 请求必须带 Cookie,否则 SSO 种下的 Cookie 无法被后续请求使用
2. **修改 `pages/Login.jsx`**
- 保留现有本地登录表单
- 新增按钮【SSO 统一登录】,点击跳转 `http://129.211.173.87:9000/?redirect=http://129.211.173.87:5173`
- 页面加载时:如果 localStorage 没有 token,先调用 `authApi.getMe()` 试探(后端可能通过 Cookie 已识别用户),若成功则自动进入后台
## 验证步骤
1. 启动 SSO 中心:`python usercenter/app.py` → 监听 :9000
2. 启动 blog 后端:`python personal-blog/backend/app.py` → 监听 :8889
3. 启动 blog 前端:`cd personal-blog/frontend && npm run dev` → :5173
4. 浏览器访问 `http://129.211.173.87:5173`
5. 点击【SSO 统一登录】,在 :9000 输入 admin/admin123 登录
6. 自动跳转回 :5173,进入后台
7. 打开 `http://129.211.173.87:9000/api/userinfo`,应返回已登录用户信息
8. 调用 `http://129.211.173.87:9000/api/logout`,再访问 blog 应返回 401