# WWW 门户 SSO 接入方案 ## 1. 现状分析 ### 1.1 www 项目当前架构 - **纯前端 SPA**,单文件 `index.html`,由 nginx 80 端口静态托管 - **无后端**,所有逻辑(认证、数据增删)均在客户端 JS 完成 - 当前认证方式: - 硬编码账号 `admin / abcd1234` - 密码经 SHA-256 哈希后存入 `localStorage` - 登录态由 `localStorage.auth` 标记 - 已有功能:登录、修改密码、添加链接、删除链接 ### 1.2 SSO 中心(usercenter)现状 - FastAPI 服务,运行在 `:9000` - Cookie 配置:`Domain=129.211.173.87`,`SameSite=Lax`,`HttpOnly=True` - 提供的 API: - `GET /` — 登录页(支持 `?redirect=` 参数) - `POST /api/login` — 登录,种 Cookie - `POST /api/logout` — 登出,清 Cookie - `GET /api/userinfo` — 返回 `{"username":"admin"}`(需 Cookie 认证) - `POST /api/change-password` — 修改密码 - CORS 当前仅允许 `:8888`、`:8889`、`:9000` ### 1.3 关键差异 | 项目 | personal-blog | www | |------|--------------|-----| | 后端 | FastAPI (`:8889`) | **无后端** | | 认证方式 | Bearer Token + SSO Cookie 双模式 | 纯 localStorage | | 接入难度 | 中(需改后端认证逻辑) | 低(仅改前端 JS) | ## 2. 接入方案 ### 2.1 核心思路 www 项目没有后端,无法做服务端 Cookie 验签。采用 **前端 API 探测** 方案: - 页面加载时,JS 跨域调用 SSO 中心 `/api/userinfo`(携带 Cookie) - 返回 200 → 已登录 → 展示管理功能 - 返回 401 → 未登录 → 展示 SSO 登录按钮 - 登录按钮跳转 SSO 中心,登录后 redirect 回门户 ### 2.2 认证流程 ``` 用户访问 http://129.211.173.87/ (门户首页) │ ▼ JS 调用 SSO /api/userinfo(credentials: 'include') │ ┌────┴────┐ │ │ 200 OK 401 │ │ ▼ ▼ 展示 展示 管理UI 「SSO 登录」按钮 │ ▼ 点击 跳转 http://129.211.173.87:9000/?redirect=http://129.211.173.87/ │ ▼ SSO 登录成功 浏览器种 Cookie → redirect 回门户 │ ▼ 页面重新加载 → JS 再调 /api/userinfo → 200 → 展示管理UI ``` ### 2.3 跨域 Cookie 可行性分析 | 条件 | 值 | 结论 | |------|---|------| | SSO Cookie Domain | `129.211.173.87` | ✅ www (`:80`) 同 IP,浏览器会带 Cookie | | SameSite | `Lax` | ✅ 顶级导航跳转(redirect)带 Cookie;跨域 fetch 需 `credentials: 'include'` | | CORS `allow_credentials` | `True` | ✅ SSO 中心已开启 | | CORS `allow_origins` | 需追加 `http://129.211.173.87` | ⚠️ **需修改** | > **注意**:`SameSite=Lax` 下,跨域 `fetch` + `credentials: 'include'` 在部分浏览器版本中行为不一致。但当前场景中,用户从 SSO 中心 redirect 回门户时是**顶级导航**,Cookie 一定会带上。fetch 探测仅用于页面加载后的状态检查,即使偶尔探测失败,刷新页面即可恢复。 ## 3. 具体改动 ### 3.1 SSO 中心(usercenter)— 1 处改动 **文件**:`/home/ubuntu/nanocode/usercenter/app.py` ```python # CORS allow_origins 新增一行 allow_origins=[ "http://129.211.173.87:8888", "http://129.211.173.87:8889", "http://129.211.173.87:9000", "http://129.211.173.87", # ← 新增:www 门户(80 端口) ], ``` ### 3.2 www 门户 — 1 个文件改动 **文件**:`/home/ubuntu/nanocode/www/index.html` #### 改动清单 | # | 改动 | 说明 | |---|------|------| | 1 | 删除 `hashPass` / `checkPass` / `genId` 等本地认证函数 | 不再需要 | | 2 | 删除 `localStorage` 中 `auth` 相关的读写 | 不再需要 | | 3 | `isLoggedIn()` 改为基于 SSO 状态变量 | 异步检测,非同步读 localStorage | | 4 | 新增 `checkSSOStatus()` | 调用 `http://129.211.173.87:9000/api/userinfo`,`credentials: 'include'` | | 5 | 页面加载时调用 `checkSSOStatus()` | 替代原 `updateNavbar()` 的同步逻辑 | | 6 | 登录按钮改为跳转 SSO | `window.location.href = 'http://129.211.173.87:9000/?redirect=...'` | | 7 | 删除「修改密码」入口 | 密码管理统一到 SSO 中心 | | 8 | 退出改为调用 SSO `/api/logout` | `fetch('.../api/logout', {method:'POST', credentials:'include'})` | | 9 | 删除登录弹窗 HTML | 不再需要本地登录表单 | | 10 | 删除修改密码弹窗 HTML | 不再需要 | #### 核心代码示意 ```javascript // SSO 配置 const SSO_BASE = 'http://129.211.173.87:9000'; let ssoUser = null; // 当前 SSO 登录用户名 // 检测 SSO 登录状态 async function checkSSOStatus() { try { const res = await fetch(`${SSO_BASE}/api/userinfo`, {credentials: 'include'}); if (res.ok) { const data = await res.json(); ssoUser = data.username; } else { ssoUser = null; } } catch { ssoUser = null; } updateNavbar(); } // SSO 登录(跳转) function ssoLogin() { const redirect = window.location.origin + window.location.pathname; window.location.href = `${SSO_BASE}/?redirect=${encodeURIComponent(redirect)}`; } // SSO 登出 async function ssoLogout() { await fetch(`${SSO_BASE}/api/logout`, {method: 'POST', credentials: 'include'}); ssoUser = null; updateNavbar(); } // 是否已登录(SSO) function isLoggedIn() { return ssoUser !== null; } // 页面加载 checkSSOStatus(); renderCards(); ``` #### 导航栏 UI 变化 **未登录时**: - 按钮:「SSO 统一登录」(跳转 SSO 中心) **已登录时**: - 显示用户名 - 按钮:「添加链接」「退出」 - 移除「修改密码」(统一在 SSO 用户中心管理) ## 4. 实施步骤 ### Step 1:修改 SSO 中心 CORS ```bash # 编辑 /home/ubuntu/nanocode/usercenter/app.py # allow_origins 增加 "http://129.211.173.87" # 重启 usercenter 服务 sudo systemctl restart usercenter ``` ### Step 2:修改 www 门户 index.html ``` 1. 删除:本地认证相关 JS 函数(hashPass、checkPass、genId、doLogin、doChangePass) 2. 删除:localStorage auth 读写函数(setAuth、clearAuth、getAuth) 3. 删除:登录弹窗 HTML(loginModal) 4. 删除:修改密码弹窗 HTML(passModal) 5. 新增:SSO 状态检测函数 checkSSOStatus() 6. 新增:ssoLogin()、ssoLogout() 函数 7. 修改:isLoggedIn() → 基于 ssoUser 变量 8. 修改:updateNavbar() → 未登录显示「SSO 统一登录」,已登录显示用户名 + 退出 9. 修改:页面初始化 → checkSSOStatus() 替代同步 updateNavbar() ``` ### Step 3:验证测试 ```bash # 1. 验证 SSO CORS 配置 curl -s -H "Origin: http://129.211.173.87" \ -H "Cookie: access_token=" \ http://129.211.173.87:9000/api/userinfo # 2. 浏览器测试 # - 访问 http://129.211.173.87/ → 应显示未登录状态 # - 点击「SSO 统一登录」→ 跳转到 :9000 登录页 # - 输入 admin / abcd1234 → 登录成功 redirect 回门户 # - 门户应显示已登录状态,可添加/删除链接 # - 点击「退出」→ 回到未登录状态 ``` ### Step 4:清理遗留 ```bash # 清除浏览器 localStorage 中旧的 auth 数据 # (无需手动处理,新版代码不再读取旧 auth key,用户清除浏览器缓存即可) ``` ## 5. 不改动的部分 - **链接数据**(`localStorage.links`)— 保持不变,与认证无关 - **卡片渲染/3D 倾斜** — 保持不变 - **nginx 配置** — 保持不变 - **SSO 中心核心逻辑** — 仅改 CORS,不动登录/验签/密码逻辑 - **personal-blog 项目** — 无需改动 ## 6. 风险与注意事项 | 风险 | 应对 | |------|------| | 跨域 fetch 携带 Cookie 在部分浏览器可能失败 | 顶级导航(SSO redirect 回来)后页面刷新自动带 Cookie,fetch 仅作辅助检测 | | SSO 中心宕机导致门户无法判断登录态 | `checkSSOStatus()` 捕获异常后按未登录处理,不影响门户浏览;管理功能暂不可用 | | `localStorage.links` 数据无服务端保护 | 原本就是纯前端存储,SSO 接入不改变此现状;后续可考虑升级为后端存储 |