diff --git a/technical/sso.md b/technical/sso.md
new file mode 100644
index 0000000..d029ce3
--- /dev/null
+++ b/technical/sso.md
@@ -0,0 +1,95 @@
+---
+tags: [sso, auth, architecture]
+create time: 2026-07-13 10:03
+---
+
+# SSO 单点登录
+
+## 概述
+
+SSO(Single Sign-On,单点登录)是一种身份认证方案:用户只需登录一次,即可访问所有相互信任的应用系统。核心目标是**统一身份源、降低登录摩擦、集中安全管控**。
+
+> [!info] 什么时候需要 SSO?
+> 当你的组织内有 3 个以上的应用需要用户登录时,就该认真考虑 SSO 了。否则每多一个系统就多一套账号密码,用户体验和安全管控都是灾难。
+
+## 核心原理
+
+SSO 的本质是一个**认证委托**模型:
+
+```mermaid
+sequenceDiagram
+ participant U as 用户
+ participant App as 业务应用
+ participant IdP as 身份提供商 (IdP)
+
+ U->>App: 1. 访问受保护资源
+ App->>IdP: 2. 重定向到登录页
+ U->>IdP: 3. 输入凭证
+ IdP-->>U: 4. 颁发 SSO Token / Session
+ IdP->>App: 5. 回调 + 认证凭证
+ App->>IdP: 6. 验证凭证有效性
+ IdP-->>App: 7. 返回用户身份信息
+ App-->>U: 8. 授予访问权限
+```
+
+三个核心角色:
+
+| 角色 | 全称 | 职责 |
+|------|------|------|
+| **User** | End User | 发起请求的终端用户 |
+| **SP** | Service Provider(服务提供方) | 业务应用,依赖 IdP 做认证 |
+| **IdP** | Identity Provider(身份提供商) | 统一认证中心,管理用户身份 |
+
+> [!tip] SP vs IdP
+> 简单记忆:IdP 知道「你是谁」,SP 知道「你能干什么」。SP 把「你是谁」这件事委托给了 IdP。
+
+## 主流协议概览
+
+| 协议 | 时代 | 传输方式 | 典型场景 |
+|------|------|----------|----------|
+| CAS | 2000+ | 浏览器重定向 + Ticket | 企业内部门户、高校系统 |
+| SAML 2.0 | 2005+ | XML + 浏览器重定向 | 企业级 SSO、SaaS 集成 |
+| OAuth 2.0 | 2012+ | HTTPS API | 第三方授权、API 访问控制 |
+| OIDC | 2014+ | HTTPS API (JWT) | 现代 Web / 移动端 / SPA |
+
+详细文档见各子笔记:
+
+- [[sso/sso-oauth2|OAuth 2.0 授权码模式]]
+- [[sso/sso-oidc|OpenID Connect (OIDC)]]
+- [[sso/sso-saml|SAML 2.0]]
+- [[sso/sso-cas|CAS 协议]]
+- [[sso/sso-comparison|协议对比与选型指南]]
+
+## 关键概念
+
+### Session 与 Token 的区别
+
+| 维度 | Session-Cookie | Token (JWT) |
+|------|---------------|-------------|
+| 存储位置 | 服务端 | 客户端 |
+| 扩展性 | 需要共享 Session Store | 天然无状态 |
+| 跨域 | Cookie 受同源策略限制 | Header 传递,无跨域问题 |
+| 撤销 | 删除 Session 即可 | 需要黑名单或短过期时间 |
+
+### SSO 的两种实现思路
+
+**1. 共享 Session(Cookie Domain 共享)**
+
+IdP 登录后写入一个顶级域的 Cookie,所有 SP 通过访问 IdP 的验证端点来确认登录状态。实现简单但受限于同主域。
+
+**2. 分布式 Token(标准协议方式)**
+
+IdP 颁发 Token,SP 独立验证。跨域、跨平台无障碍,是当前主流方案。
+
+> [!warning] 安全要点
+> - 始终使用 HTTPS,Token 一旦泄露等同于身份被盗
+> - Token 有效期不宜过长,Access Token 建议 5-30 分钟
+> - 使用 `state` 参数防 CSRF,`nonce` 参数防重放攻击
+
+## 关联笔记
+
+- [[sso/sso-oauth2|OAuth 2.0 授权码模式]]
+- [[sso/sso-oidc|OpenID Connect (OIDC)]]
+- [[sso/sso-saml|SAML 2.0]]
+- [[sso/sso-cas|CAS 协议]]
+- [[sso/sso-comparison|协议对比与选型指南]]
diff --git a/technical/sso/sso-cas.md b/technical/sso/sso-cas.md
new file mode 100644
index 0000000..5b4b0f7
--- /dev/null
+++ b/technical/sso/sso-cas.md
@@ -0,0 +1,179 @@
+---
+tags: [sso, cas, auth]
+create time: 2026-07-13 10:03
+---
+
+# CAS 协议
+
+## 概述
+
+CAS(Central Authentication Service)是由耶鲁大学于 2000 年开发的 SSO 协议,后由 Apereo 基金会维护。它是最早的 SSO 标准之一,设计简洁,在高校和企业内部门户中仍有广泛应用。当前版本为 CAS 3.0(CAS Protocol Specification)。
+
+> [!info] CAS 的定位
+> 相比 SAML 的重量级 XML 和 OIDC 的 JWT 生态,CAS 的核心优势是**简单**。整个协议可以用几段 HTTP 请求描述清楚,实现成本极低。如果你只需要企业内部几个 Web 应用的 SSO,CAS 是最省事的选择。
+
+## 核心流程
+
+CAS 的核心思路是 **Ticket 机制**:用户在 CAS Server 登录后拿到一个一次性的 Service Ticket,业务应用用这个 Ticket 去 CAS Server 换取用户身份。
+
+```mermaid
+sequenceDiagram
+ participant U as 用户 (浏览器)
+ participant App as 业务应用 (CAS Client)
+ participant CAS as CAS Server
+
+ U->>App: 1. 访问受保护页面
+ App->>U: 302 → CAS Server
+ Note right of App: service=https://app.example.com/callback
+ U->>CAS: 2. 重定向到 CAS 登录页
+ CAS->>U: 3. 展示登录表单
+ U->>CAS: 4. 提交凭证
+ CAS->>CAS: 5. 验证凭证
+ CAS->>U: 302 → service URL + Ticket
+ Note left of CAS: ?ticket=ST-12345-xxxxx
+ U->>App: 6. 携带 Ticket 回到应用
+ App->>CAS: 7. 后端验证 Ticket
+ Note right of App: GET /serviceValidate?ticket=ST-xxx&service=...
+ CAS-->>App: 8. 返回用户信息 (XML)
+ App-->>U: 9. 建立本地 Session,登录成功
+```
+
+## Ticket 类型
+
+CAS 协议定义了多种 Ticket,各有不同用途:
+
+| Ticket | 前缀 | 生命周期 | 用途 |
+|--------|------|----------|------|
+| **TGT** | `TGT-` | 用户会话级 | CAS Server 的登录凭证,存在 Server 端 |
+| **ST** | `ST-` | 一次性,< 10s | SP 验证用户身份用,用后即毁 |
+| **PT** | `PT-` | 一次性 | Proxy Ticket,代理场景用 |
+| **PGT** | `PGT-` | 较长 | Proxy Granting Ticket |
+| **PGTIOU** | `PGTIOU-` | 短 | PGT 和 ST 的关联标识 |
+
+> [!tip] 简化理解
+> 对于 90% 的场景,你只需要关心 **TGT**(用户在 CAS Server 的登录态)和 **ST**(一次性验证凭证)。其他 Ticket 是为代理认证设计的,大多数接入方用不到。
+
+### Ticket 流转关系
+
+```mermaid
+graph TD
+ A[用户登录] -->|成功| B[CAS Server 颁发 TGT]
+ B -->|写入 Cookie| C[TGT 存在 CAS Server]
+ C -->|SP 请求| D[生成 ST]
+ D -->|一次性验证| E[SP 后端验证 ST]
+ E -->|返回用户信息| F[SP 建立本地 Session]
+```
+
+## 关键接口
+
+CAS 协议定义的核心端点:
+
+| 端点 | 说明 |
+|------|------|
+| `/login` | 登录端点,支持 `service` 参数 |
+| `/logout` | 登出端点,支持 `service` 参数回跳 |
+| `/serviceValidate` | ST 验证端点(CAS 2.0) |
+| `/p3/serviceValidate` | ST 验证端点(CAS 3.0,返回更多用户属性) |
+| `/proxyValidate` | PT 验证端点 |
+| `/proxy` | 获取 PGT |
+
+### Ticket 验证响应示例
+
+**CAS 2.0:**
+```xml
+
+
+ zhangsan
+
+
+```
+
+**CAS 3.0(支持属性释放):**
+```xml
+
+
+ zhangsan
+
+ zhangsan@example.com
+ 张三
+ engineering
+
+
+
+```
+
+## Go 接入示例
+
+CAS 接入相对简单,核心逻辑就是拦截请求 + 验证 Ticket:
+
+```go
+// CAS Client 核心逻辑
+func CASServerURL = "https://cas.example.com"
+func ServiceURL = "https://app.example.com"
+
+func CASMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // 1. 检查本地 Session
+ if session, _ := store.Get(r, "session"); session.Values["user"] != nil {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ // 2. 检查 URL 中的 Ticket
+ ticket := r.URL.Query().Get("ticket")
+ if ticket == "" {
+ // 3. 没有 Ticket → 重定向到 CAS
+ loginURL := CASServerURL + "/login?service=" + url.QueryEscape(ServiceURL+r.URL.Path)
+ http.Redirect(w, r, loginURL, http.StatusFound)
+ return
+ }
+
+ // 4. 后端验证 Ticket
+ validateURL := fmt.Sprintf(
+ "%s/p3/serviceValidate?ticket=%s&service=%s",
+ CASServerURL, ticket, url.QueryEscape(ServiceURL),
+ )
+ resp, err := http.Get(validateURL)
+ if err != nil {
+ http.Error(w, "CAS validation failed", 500)
+ return
+ }
+ defer resp.Body.Close()
+
+ // 5. 解析 XML 响应,提取用户名
+ // ... 解析 logic ...
+
+ // 6. 建立本地 Session
+ session, _ := store.Get(r, "session")
+ session.Values["user"] = parsedUser
+ session.Save(r, w)
+
+ // 7. 重定向去掉 Ticket 参数
+ cleanURL := strings.Split(r.URL.String(), "?")[0]
+ http.Redirect(w, r, cleanURL, http.StatusFound)
+ })
+}
+```
+
+## 常见陷阱与最佳实践
+
+**Ticket 不能重复验证**
+- ST 验证一次后即失效,不要缓存 Ticket 验证结果
+- 用户刷新页面时 Ticket 已经失效,应该走 Session 而不是重新验证
+
+**service 参数必须严格匹配**
+- CAS Server 会校验 service 参数是否在注册列表中
+- 不要拼接用户可控的内容到 service 参数
+
+**登出的局限性**
+- CAS 的 `/logout` 只能清除 TGT(CAS Server 端的登录态)
+- 各 SP 的本地 Session 需要各自处理,CAS 通过回调通知 SP(Back-channel 或 Front-channel)
+- 实际部署中,很多 SP 不实现登出回调,导致用户以为登出了但 SP Session 仍有效
+
+**CAS vs OIDC 的选择**
+- 如果你的系统只需要内网 Web 应用 SSO,CAS 足够
+- 如果需要支持移动端、SPA、第三方集成,直接上 OIDC
+- 很多现代 CAS Server(如 Apereo CAS 6.x)同时支持 CAS 协议和 OIDC/SAML
+
+> [!tip] Apereo CAS Server 的现代化
+> Apereo CAS Server 6.x+ 已经不仅仅是 CAS 协议服务器了。它同时支持 CAS / OIDC / SAML / REST API,可以作为统一身份网关使用。如果你需要一个开源自建的 SSO 平台,值得考虑。
diff --git a/technical/sso/sso-comparison.md b/technical/sso/sso-comparison.md
new file mode 100644
index 0000000..7ac0ea3
--- /dev/null
+++ b/technical/sso/sso-comparison.md
@@ -0,0 +1,133 @@
+---
+tags: [sso, architecture, auth]
+create time: 2026-07-13 10:03
+---
+
+# SSO 协议对比与选型指南
+
+## 概述
+
+本文横向对比 CAS、SAML 2.0、OAuth 2.0、OIDC 四种主流 SSO 协议,帮助你在实际项目中做出合理的选型决策。
+
+## 横向对比总览
+
+| 维度 | CAS | SAML 2.0 | OAuth 2.0 | OIDC |
+|------|-----|----------|-----------|------|
+| **诞生年份** | 2000 | 2005 | 2012 | 2014 |
+| **数据格式** | 自定义 XML | XML | JSON | JSON (JWT) |
+| **传输层** | HTTP 重定向 | HTTP 重定向 + POST | HTTPS API | HTTPS API |
+| **核心目的** | 认证 (SSO) | 认证 (SSO) | 授权 | 认证 + 授权 |
+| **Token 类型** | Ticket (ST/TGT) | Assertion | access_token | id_token + access_token |
+| **移动端支持** | 差 | 差 | 好 | 优秀 |
+| **SPA 支持** | 差 | 差 | 好 (PKCE) | 优秀 (PKCE) |
+| **实现复杂度** | 低 | 高 | 中 | 中 |
+| **生态丰富度** | 一般 | 丰富 (企业级) | 极丰富 | 极丰富 |
+| **主要用户** | 高企内部系统 | 企业 B2B SaaS | 第三方登录 | 现代 Web/移动应用 |
+
+## 各协议适用场景
+
+### 选择 CAS 的场景
+
+- 企业内部多个传统 Web 系统需要统一登录
+- 已有 Apereo CAS Server 基础设施
+- 团队对 OAuth/JWT 不熟悉,需要快速落地
+- 不涉及移动端或第三方接入
+
+### 选择 SAML 2.0 的场景
+
+- 与企业客户的 IdP(AD FS、Okta)对接
+- 已有 AD/LDAP 用户体系,需要对外暴露 SSO
+- B2B SaaS 产品,客户要求 SAML 集成
+- 合规要求(如 FedRAMP、SOC 2)强制使用 SAML
+
+### 选择 OAuth 2.0 的场景
+
+- 第三方应用需要有限度地访问你的 API
+- 「用微信/GitHub 登录」这类需求
+- 纯 API 服务的访问控制(客户端凭证模式)
+- 服务间的 M2M 认证
+
+### 选择 OIDC 的场景
+
+- **新项目的首选**——现代、通用、生态好
+- SPA / 移动端 / 微服务架构
+- 需要标准化的用户信息接口
+- 需要和主流 IdP(Keycloak、Auth0、Azure AD)对接
+
+## 技术栈对比
+
+### 数据格式
+
+| 格式 | 优点 | 缺点 |
+|------|------|------|
+| XML (SAML) | 严谨、标准完善 | 冗长、解析慢、安全风险多 |
+| JSON (OAuth/OIDC) | 轻量、开发者友好 | 规范相对灵活,需自行约束 |
+| JWT (OIDC ID Token) | 自包含、可离线验证 | 无法即时撤销、Payload 可见 |
+
+### 签名与加密
+
+| 协议 | 签名方式 | 加密支持 |
+|------|----------|----------|
+| CAS | 可选 (HMAC) | 不支持 |
+| SAML | XML Digital Signature(RSA/EC) | XML Encryption(RSA/AES) |
+| OAuth 2.0 | 无标准(靠 HTTPS) | 无标准 |
+| OIDC | JWT Signature(RS256/ES256) | JWE(可选) |
+
+> [!info] 为什么 OIDC 通常只签名不加密?
+> JWT 的 Payload 通常是用户 ID、邮箱等非敏感信息。签名保证完整性(不被篡改),HTTPS 保证传输安全。如果需要隐藏 Payload,使用 JWE 加密。
+
+## 选型决策树
+
+```mermaid
+graph TD
+ A[需要 SSO] --> B{有移动端/SPA?}
+ B -->|是| C[OIDC]
+ B -->|否| D{对接企业客户 IdP?}
+ D -->|是| E{客户要求?}
+ E -->|SAML| F[SAML 2.0]
+ E -->|OIDC| C
+ E -->|无要求| C
+ D -->|否| G{内部系统?}
+ G -->|是| H{已有基础设施?}
+ H -->|CAS Server| I[CAS]
+ H -->|Keycloak| C
+ H -->|无| C
+ G -->|否| J{第三方授权?}
+ J -->|是| K[OAuth 2.0]
+ J -->|否| C
+```
+
+> [!tip] 默认选 OIDC
+> 如果你没有强烈的理由选择其他协议,**OIDC 是最安全的默认选择**。它覆盖了 SSO + 授权的完整场景,生态支持最好,学习资源最丰富。
+
+## 接入成本评估
+
+| 维度 | CAS | SAML | OAuth 2.0 | OIDC |
+|------|-----|------|-----------|------|
+| **Go 库成熟度** | 一般 | 一般 | 优秀 | 优秀 |
+| **调试难度** | 低 | 高(XML 解析坑多) | 中 | 中 |
+| **文档质量** | 一般 | 规范详尽但晦涩 | 优秀 | 优秀 |
+| **测试工具** | 少 | SAML Tracer 等 | OAuth Proxy | OIDC Debugger |
+| **预计接入工时** | 1-3 天 | 3-7 天 | 1-3 天 | 1-3 天 |
+
+## 推荐开源 IdP
+
+| 产品 | 支持协议 | 特点 | 适用规模 |
+|------|----------|------|----------|
+| **Keycloak** | OIDC, SAML | 功能全面,Red Hat 维护 | 中大型 |
+| **Auth0** | OIDC, SAML | SaaS 托管,上手快 | 中小型 |
+| **Casdoor** | OIDC, SAML, CAS | Go 实现,中文社区 | 中小型 |
+| **Apereo CAS** | CAS, OIDC, SAML | 高校/企业经典选择 | 中大型 |
+| **Authentik** | OIDC, SAML | Python,现代化 UI | 中小型 |
+| **Logto** | OIDC | 开源 Auth0 替代 | 中小型 |
+
+> [!tip] Keycloak 是自建 SSO 的首选
+> 如果要自建 SSO 平台,Keycloak 是当前综合评分最高的开源方案。支持 OIDC + SAML,管理界面完善,社区活跃。唯一的缺点是基于 Java,内存占用较大。
+
+## 关联笔记
+
+- [[sso/sso-oauth2|OAuth 2.0 授权码模式]]
+- [[sso/sso-oidc|OpenID Connect (OIDC)]]
+- [[sso/sso-saml|SAML 2.0]]
+- [[sso/sso-cas|CAS 协议]]
+- [[sso|SSO 单点登录(主文档)]]
diff --git a/technical/sso/sso-oauth2.md b/technical/sso/sso-oauth2.md
new file mode 100644
index 0000000..3607f0f
--- /dev/null
+++ b/technical/sso/sso-oauth2.md
@@ -0,0 +1,142 @@
+---
+tags: [sso, oauth2, auth]
+create time: 2026-07-13 10:03
+---
+
+# OAuth 2.0 授权码模式
+
+## 概述
+
+OAuth 2.0 是一个**授权框架**(RFC 6749),设计初衷是让第三方应用在不获取用户密码的前提下,有限度地访问用户的资源。授权码模式(Authorization Code Grant)是最安全、最常用的流程,也是构建 SSO 的基础。
+
+> [!info] OAuth 2.0 ≠ 认证协议
+> OAuth 2.0 解决的是「**授权**」—— 让第三方拿走你的部分权限。它本身不提供用户身份信息,这也是为什么后来需要 OIDC 来补上这一环。
+
+## 核心流程
+
+```mermaid
+sequenceDiagram
+ participant U as 用户 (浏览器)
+ participant App as Client App (SP)
+ participant Auth as Authorization Server (IdP)
+ participant Res as Resource Server
+
+ U->>App: 1. 点击「登录」
+ App->>U: 2. 302 重定向到 Auth
+ Note right of App: redirect_uri, scope, state, client_id
+ U->>Auth: 3. 登录 + 授权确认
+ Auth->>U: 4. 302 回调 redirect_uri
+ Note left of Auth: ?code=AUTH_CODE&state=xxx
+ U->>App: 5. 携带 code 回到 App
+ App->>Auth: 6. POST /token(后端请求)
+ Note right of App: code, client_id, client_secret, redirect_uri
+ Auth-->>App: 7. 返回 access_token
+ App->>Res: 8. 用 access_token 请求资源
+ Res-->>App: 9. 返回受保护数据
+```
+
+关键点:**步骤 2-5 走浏览器重定向,步骤 6-7 走后端 HTTPS 直连**。code 只能用一次,且必须在后端交换,避免 token 暴露在前端。
+
+## 四种授权模式对比
+
+| 模式 | 流程 | 安全性 | 适用场景 |
+|------|------|--------|----------|
+| **授权码模式** | 重定向 + 后端换 Token | 最高 | Web 后端应用、SSO |
+| 授权码 + PKCE | 授权码 + 代码验证码 | 高 | SPA / 移动端(推荐) |
+| 客户端凭证模式 | 直接换 Token | 中 | 服务间调用(M2M) |
+| ~~隐式模式~~ | 直接返回 Token | 低 | **已弃用**,仅遗留系统 |
+
+> [!warning] 隐式模式已被废弃
+> RFC 9700 明确不推荐使用隐式模式。Token 暴露在 URL Fragment 中,容易被中间人和浏览器历史记录泄露。SPA 应使用 **PKCE** 模式替代。
+
+## 核心概念速查
+
+### 四个关键角色
+
+| 角色 | 职责 | 示例 |
+|------|------|------|
+| Resource Owner | 资源拥有者(用户) | 登录的你 |
+| Client | 第三方应用 | 公司的 Web 系统 |
+| Authorization Server | 授权服务器 | Keycloak、Auth0 |
+| Resource Server | 资源服务器 | 你的 API 服务 |
+
+### 关键参数
+
+| 参数 | 说明 |
+|------|------|
+| `client_id` | 应用注册时分配的标识 |
+| `client_secret` | 应用密钥,仅后端持有 |
+| `redirect_uri` | 回调地址,必须预注册 |
+| `scope` | 请求的权限范围,如 `openid profile email` |
+| `state` | 防 CSRF 的随机字符串,回调时原样返回 |
+| `code` | 授权码,一次性,有效期通常 < 10 分钟 |
+
+### Access Token vs Refresh Token
+
+```mermaid
+graph LR
+ A[Access Token] -->|有效期短 5-30min| B[访问 API]
+ C[Refresh Token] -->|有效期长 天/月| D[换取新 Access Token]
+ C -.->|轮换机制| C2[新 Refresh Token]
+```
+
+| Token | 用途 | 存储 | 有效期 |
+|-------|------|------|--------|
+| Access Token | 调用 API | 内存 / 安全 Cookie | 短(5-30 min) |
+| Refresh Token | 刷新 Access Token | HttpOnly Cookie / 后端 | 长(天 ~ 月) |
+
+> [!tip] Refresh Token Rotation
+> 每次用 Refresh Token 换新 Token 时,同时颁发新的 Refresh Token 并使旧的失效。这样即使某个 Refresh Token 泄露,攻击者也只能用一次。
+
+## Go 实现示例
+
+使用 `golang.org/x/oauth2` 实现授权码流程的核心步骤:
+
+```go
+// 配置 OAuth2 客户端
+conf := &oauth2.Config{
+ ClientID: "your-client-id",
+ ClientSecret: "your-client-secret",
+ Scopes: []string{"openid", "profile", "email"},
+ Endpoint: oauth2.Endpoint{
+ AuthURL: "https://idp.example.com/oauth/authorize",
+ TokenURL: "https://idp.example.com/oauth/token",
+ },
+ RedirectURL: "https://app.example.com/callback",
+}
+
+// 1. 生成授权 URL(附带 state 防 CSRF)
+state := generateRandomState() // 需要存入 session
+url := conf.AuthCodeURL(state, oauth2.AccessTypeOffline)
+
+// 2. Callback 处理 — 用 code 换 Token
+token, err := conf.Exchange(ctx, code)
+if err != nil {
+ // 授权码过期或无效
+ log.Fatal(err)
+}
+
+// 3. 用 Token 请求用户信息
+client := conf.Client(ctx, token)
+resp, err := client.Get("https://idp.example.com/userinfo")
+```
+
+> `oauth2.AccessTypeOffline` 参数会请求颁发 Refresh Token。默认只发 Access Token。
+
+## 常见陷阱与最佳实践
+
+**CSRF 攻击**
+- 必须使用 `state` 参数。生成随机值存入 session,回调时校验一致性
+- 不用 `state` 就像进门不锁门
+
+**Token 存储**
+- 前端:不要存在 `localStorage`(XSS 可读取),用 HttpOnly Secure Cookie
+- 后端:加密存储或存入 Session,不要写进数据库明文字段
+
+**redirect_uri 必须严格匹配**
+- 不支持通配符,不支持路径前缀匹配
+- 生产环境不允许使用 `http://localhost`
+
+**scope 最小化原则**
+- 只请求你需要的权限,不要 `scope: "all"`
+- 用户看到的授权页面 scope 越少,信任度越高
diff --git a/technical/sso/sso-oidc.md b/technical/sso/sso-oidc.md
new file mode 100644
index 0000000..3a5f22f
--- /dev/null
+++ b/technical/sso/sso-oidc.md
@@ -0,0 +1,166 @@
+---
+tags: [sso, oidc, jwt, auth]
+create time: 2026-07-13 10:03
+---
+
+# OpenID Connect (OIDC)
+
+## 概述
+
+OpenID Connect(OIDC)是构建在 OAuth 2.0 之上的**身份认证层**。如果说 OAuth 2.0 解决「你能访问什么」,OIDC 则补充了「你是谁」。它是当前最主流的 SSO 协议,几乎所有现代身份提供商(Keycloak、Auth0、Okta、Azure AD)都支持 OIDC。
+
+> [!info] 为什么需要 OIDC?
+> OAuth 2.0 的授权码拿到的是 access_token,它可以调 API,但你不知道**登录的用户是谁**。应用只能再去调 `/userinfo` 接口,流程不统一。OIDC 通过引入 **ID Token** 直接告诉应用用户的身份。
+
+## OIDC vs OAuth 2.0 的区别
+
+| 维度 | OAuth 2.0 | OIDC |
+|------|-----------|------|
+| 本质 | 授权框架 | 认证 + 授权 |
+| Token | access_token, refresh_token | **+ id_token (JWT)** |
+| 用户信息 | 需要额外请求 /userinfo | ID Token 自带 + /userinfo 补充 |
+| scope | 自定义 | 必须含 `openid`,标准 scope: `profile`, `email`, `address`, `phone` |
+| 规范 | RFC 6749 | OpenID Connect Core 1.0 |
+
+## 核心流程
+
+OIDC 的授权码流程和 OAuth 2.0 几乎一致,区别在于 scope 包含 `openid`,返回值多了 `id_token`:
+
+```mermaid
+sequenceDiagram
+ participant U as 用户
+ participant App as Client (RP)
+ participant IdP as OpenID Provider
+
+ U->>App: 1. 点击登录
+ App->>U: 2. 302 → IdP
+ Note right of App: scope=openid profile email
+ U->>IdP: 3. 认证 + 授权
+ IdP->>U: 4. 302 → redirect_uri
+ Note left of IdP: code=xxx & state=yyy
+ U->>App: 5. 回调
+ App->>IdP: 6. POST /token
+ IdP-->>App: 7. { access_token, id_token, refresh_token }
+ App->>App: 8. 验证 id_token 签名 + claims
+ App-->>U: 9. 登录成功
+```
+
+## ID Token 详解
+
+ID Token 是一个 **JWT(JSON Web Token)**,包含用户身份信息,由 IdP 用私钥签名。
+
+### 结构
+
+JWT 由三部分组成:`Header.Payload.Signature`
+
+**Header:**
+```json
+{
+ "alg": "RS256",
+ "typ": "JWT",
+ "kid": "key-id-2026"
+}
+```
+
+**Payload(Claims):**
+```json
+{
+ "iss": "https://idp.example.com",
+ "sub": "user-12345",
+ "aud": "your-client-id",
+ "exp": 1752374580,
+ "iat": 1752374280,
+ "nonce": "random-nonce-value",
+ "name": "张三",
+ "email": "zhangsan@example.com",
+ "email_verified": true,
+ "picture": "https://cdn.example.com/avatar.jpg"
+}
+```
+
+### 必须校验的 Claims
+
+> [!danger] 不校验 = 门户大开
+> 每一个 Claim 都有其安全意义,跳过任何一个都可能导致身份伪造。
+
+| Claim | 校验规则 | 不校验的风险 |
+|-------|----------|-------------|
+| `iss` | 必须是你配置的 IdP 地址 | 接受恶意 IdP 签发的 Token |
+| `aud` | 必须包含你的 `client_id` | Token 被别的应用盗用 |
+| `exp` | 必须未过期 | 过期 Token 仍可使用 |
+| `nonce` | 必须和请求时一致 | 重放攻击 |
+| `signature` | 用 IdP 公钥验证 | Token 内容被篡改 |
+
+### 校验流程
+
+```go
+// 使用 OIDC 库自动校验 ID Token
+provider, err := oidc.NewProvider(ctx, "https://idp.example.com")
+
+verifier := provider.Verifier(&oidc.Config{
+ ClientID: "your-client-id",
+})
+
+// 解析并校验 ID Token(签名、iss、aud、exp、nonce 全自动校验)
+idToken, err := verifier.Verify(ctx, rawIDToken)
+
+// 提取 Claims
+var claims struct {
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Picture string `json:"picture"`
+}
+if err := idToken.Claims(&claims); err != nil {
+ log.Fatal(err)
+}
+```
+
+> 使用 `coreos/go-oidc` 库时,签名验证会自动从 IdP 的 **JWKS 端点**(`/.well-known/jwks.json`)拉取公钥并缓存。
+
+## OIDC Discovery
+
+每个 OIDC Provider 都暴露一个**发现端点**,让客户端自动获取所有配置:
+
+```
+GET https://idp.example.com/.well-known/openid-configuration
+```
+
+返回:
+```json
+{
+ "issuer": "https://idp.example.com",
+ "authorization_endpoint": "https://idp.example.com/oauth/authorize",
+ "token_endpoint": "https://idp.example.com/oauth/token",
+ "userinfo_endpoint": "https://idp.example.com/userinfo",
+ "jwks_uri": "https://idp.example.com/.well-known/jwks.json",
+ "scopes_supported": ["openid", "profile", "email"],
+ "response_types_supported": ["code"],
+ "grant_types_supported": ["authorization_code", "refresh_token"],
+ "id_token_signing_alg_values_supported": ["RS256", "ES256"]
+}
+```
+
+> [!tip] Discovery 的好处
+> 客户端只需要知道 IdP 的 URL,所有端点地址和算法支持都可以自动发现。这极大降低了接入成本。
+
+## 常见陷阱与最佳实践
+
+**永远校验 ID Token 签名**
+- 使用 IdP 公钥(通过 JWKS 获取)验证,不要只 Base64 解码就信了
+- 公钥轮换时,库应自动从 JWKS 端点刷新
+
+**nonce 必须使用**
+- 登录请求生成 nonce 存入 session,ID Token 校验时比对
+- 防止 Token 被截获后重放
+
+**UserInfo Endpoint 的定位**
+- ID Token 已包含基本身份信息,UserInfo 用于获取额外数据
+- 不要把敏感信息放在 ID Token 里(它可能被前端持有)
+
+**多租户场景的 `hd` 参数**
+- Google OIDC 支持 `hd`(hosted domain)限制只允许特定域名登录
+- 企业 SSO 场景务必限制,防止个人账号混入
+
+**Key Rollover**
+- IdP 会定期轮换签名密钥,客户端需要支持多 Key(通过 `kid` 匹配)
+- 推荐使用自动 JWKS 刷新的库,不要硬编码公钥
diff --git a/technical/sso/sso-saml.md b/technical/sso/sso-saml.md
new file mode 100644
index 0000000..890acdc
--- /dev/null
+++ b/technical/sso/sso-saml.md
@@ -0,0 +1,192 @@
+---
+tags: [sso, saml, xml, auth]
+create time: 2026-07-13 10:03
+---
+
+# SAML 2.0
+
+## 概述
+
+SAML(Security Assertion Markup Language)2.0 是基于 XML 的**联合身份认证标准**,由 OASIS 于 2005 年发布。它是企业级 SSO 的事实标准,广泛用于 SaaS 应用集成(如 AWS、Salesforce、Workday 等),尤其在已有 LDAP/AD 的企业环境中几乎是必选项。
+
+> [!info] SAML vs OIDC 的市场定位
+> SAML 是企业时代的产物——XML 繁重但可靠,适合 B2B 场景。OIDC 是云原生时代的产物——JSON 轻量灵活,适合 B2C 和移动端。两者并存,不是替代关系。
+
+## 核心概念
+
+### 角色
+
+| 角色 | 全称 | 说明 |
+|------|------|------|
+| **IdP** | Identity Provider | 身份提供商,如 AD FS、Okta、OneLogin |
+| **SP** | Service Provider | 业务应用,依赖 IdP 做认证 |
+| **Principal** | End User | 发起 SSO 的终端用户 |
+
+### 关键数据结构
+
+**Assertion(断言)** — SAML 的核心数据单元,包含:
+
+| 字段 | 说明 |
+|------|------|
+| `Issuer` | 断言签发者(IdP) |
+| `Subject` | 被认证的用户 |
+| `Conditions` | 有效期、受众限制 |
+| `AuthnStatement` | 认证方式、时间 |
+| `AttributeStatement` | 用户属性(角色、邮箱等) |
+
+## 核心流程(SP-Initiated SSO)
+
+SP 发起的 SSO 是最常见的流程:
+
+```mermaid
+sequenceDiagram
+ participant U as 用户
+ participant SP as Service Provider
+ participant IdP as Identity Provider
+
+ U->>SP: 1. 访问受保护页面
+ SP->>U: 2. 生成 AuthnRequest,302 重定向
+ Note right of SP: SAMLRequest (Base64 + Deflate)
+ U->>IdP: 3. 重定向到 IdP SSO 端点
+ IdP->>U: 4. 展示登录页
+ U->>IdP: 5. 输入凭证
+ IdP->>IdP: 6. 验证凭证
+ IdP->>U: 7. 返回 HTML Form(自动提交)
+ Note left of IdP: SAMLResponse (Base64 XML)
+ U->>SP: 8. POST 到 ACS URL
+ SP->>SP: 9. 验证签名 + 解析 Assertion
+ SP-->>U: 10. 建立 Session,跳转目标页
+```
+
+> [!tip] 为什么用 POST 而不是 GET?
+> SAMLResponse 是一段大的 Base64 XML,超过 URL 长度限制。所以 IdP 返回一个自动提交的 HTML Form,用 POST 送到 SP 的 ACS(Assertion Consumer Service)端点。
+
+### AuthnRequest 示例
+
+```xml
+
+ https://app.example.com
+
+
+```
+
+### SAMLResponse 示例(简化)
+
+```xml
+
+ https://idp.example.com
+ ...
+
+
+
+
+ https://idp.example.com
+ ...
+
+ user@example.com
+
+
+
+
+
+
+ https://app.example.com
+
+
+
+
+
+ urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
+
+
+
+
+
+ admin
+
+
+
+
+```
+
+## SP 配置要点
+
+接入 SAML 时,你需要向 IdP 提供以下信息:
+
+| 配置项 | 说明 | 示例 |
+|--------|------|------|
+| **Entity ID** | SP 的唯一标识 | `https://app.example.com` |
+| **ACS URL** | 接收 SAMLResponse 的端点 | `https://app.example.com/saml/acs` |
+| **NameID Format** | 用户标识格式 | `emailAddress` / `persistent` |
+| **证书** | SP 的公钥(用于加密 Assertion) | PEM 格式证书 |
+
+从 IdP 获取的配置:
+
+| 配置项 | 说明 |
+|--------|------|
+| **SSO URL** | IdP 的登录端点 |
+| **SLO URL** | 单点登出端点(可选) |
+| **IdP 证书** | 用于验证 SAMLResponse 签名 |
+
+## Go 接入方案
+
+Go 生态中 SAML 库相对 OIDC 较少,推荐:
+
+```go
+// 使用 crewjam/saml 库
+import "github.com/crewjam/saml/samlsp"
+
+// 创建 SAML 中间件
+samlSP, _ := samlsp.New(samlsp.Options{
+ EntityID: "https://app.example.com",
+ URL: *rootURL,
+ Key: tlsCert.PrivateKey.(crypto.Signer),
+ Certificate: tlsCert.Leaf,
+ IDPMetadata: idpMetadataURL, // IdP 的 Metadata XML URL
+})
+
+// 注册路由
+http.Handle("/saml/", samlSP) // SAML 端点(ACS、Metadata)
+http.Handle("/protected", samlSP.RequireAccount(handler)) // 受保护路由
+```
+
+## 常见陷阱与最佳实践
+
+**XML Signature Wrapping 攻击**
+- SAML 最大的安全风险。攻击者在 XML 中插入额外的 Assertion,利用解析器的逻辑漏洞
+- 校验签名后,必须**重新提取** Subject 和 Conditions,不要信任解析位置
+
+**时钟偏移**
+- SAML Assertion 的有效期通常只有 5 分钟,IdP 和 SP 之间时钟不同步会导致验证失败
+- 建议允许 30 秒 - 2 分钟的时钟偏移容差
+
+**Logout 的复杂性**
+- SAML SLO(Single Logout)需要所有 SP 协同,实际部署中经常不工作
+- 很多企业选择**仅清除本地 Session**,不做真正的 SLO
+
+**Metadata 自动刷新**
+- IdP 的签名证书会轮换,SP 应定期拉取 IdP Metadata XML 更新证书
+- 不要硬编码 IdP 证书
+
+**NameID 的选择**
+
+| Format | 特点 | 适用场景 |
+|--------|------|----------|
+| `emailAddress` | 可读,但邮箱会变 | 外部用户 |
+| `persistent` | 不可读的随机 ID | 需要隐私保护 |
+| `transient` | 每次登录不同 | 一次性访问 |
+
+> [!warning] 不要用 NameID 做业务关联
+> NameID 是认证标识,不是业务 ID。如果用户邮箱变了,NameID 变了,你的业务系统不应该因此丢失用户数据。用一个稳定的内部 ID 做映射。