vault backup: 2026-07-13 10:08:46
This commit is contained in:
@@ -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 越少,信任度越高
|
||||
Reference in New Issue
Block a user