Files
nanocode/usercenter/docs/sso-plan.md
T
2026-08-04 16:00:01 +08:00

5.4 KiB
Raw Blame History

单台服务器多端口 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
属性 值 说明
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/zf9wx4xm7 登录
  6. 自动跳转回 :5173,进入后台
  7. 打开 http://129.211.173.87:9000/api/userinfo,应返回已登录用户信息
  8. 调用 http://129.211.173.87:9000/api/logout,再访问 blog 应返回 401