vault backup: 2026-07-13 10:08:46

This commit is contained in:
2026-07-13 10:08:46 +08:00
parent 6ff67e3fa3
commit 9865fbf3b2
6 changed files with 907 additions and 0 deletions
+142
View File
@@ -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 越少,信任度越高