--- tags: [CS, NET, security, authorization, web, authentication-flow] create time: 2026-04-17 --- # Web Application Authorization Flow ## 概述 **Authorization(授权)** 在 Web 应用中是 HTTP 层面的认证令牌验证机制。本文聚焦浏览器、服务器、Token 的交互流程和加密验证过程。 ## HTTP Authorization 流程 ### 完整授权流程 ```mermaid sequenceDiagram participant B as Browser participant S as Server participant DB as Database Note over B,S: 1. 登录获取 Token B->>S: POST /login (username, password) S->>DB: 验证凭证 DB-->>S: 用户数据 S->>S: HMAC-SHA256(payload, secret) S-->>B: {access_token: "eyJhbGc...", expires_in: 3600} Note over B,S: 2. 带授权头访问资源 B->>B: 存储 Token (LocalStorage/Memory) B->>S: GET /api/resource Note right of B: Authorization: Bearer eyJhbGc... Note over S: 3. 验证 Token S->>S: 提取 Token S->>S: 验证签名 (HMAC) S->>S: 检查过期时间 S->>S: 验证 Issuer/Audience S->>DB: 根据 user_id 查询权限 Note over S: 4. 授权决策 alt Token 有效 S-->>B: 200 OK + 数据 else Token 无效 S-->>B: 401 Unauthorized else 权限不足 S-->>B: 403 Forbidden end ``` ### JWT Token 验证过程 ```mermaid graph TD A[接收 Authorization: Bearer ] --> B[解析三部分] B --> C[Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9] B --> D[Payload: eyJ1c2VyX2lkIjoiMTIzIiw...] B --> E[Signature: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c] C --> F[获取签名算法 HS256] D --> G[获取用户数据] E --> H[签名部分] F --> I[重新计算签名] G --> I H --> I I --> J{签名匹配?} J -->|是| K[检查过期时间 exp] J -->|否| L[拒绝: 401 Unauthorized] K -->|未过期| M[检查 nbf 时间] K -->|已过期| L M -->|有效| N[检查 iss 签发者] M -->|无效| L N -->|匹配| O[验证通过] N -->|不匹配| L O --> P[从 payload 提取 user_id] P --> Q[查询用户权限] Q --> R[返回受保护资源] ``` ## 浏览器行为详解 ### 浏览器如何处理 Authorization ```mermaid graph LR A[用户请求资源] --> B[检查 Token] B --> C{Token 存在于?} C -->|LocalStorage| D[从 LS 读取] C -->|SessionStorage| E[从 SS 读取] C -->|Memory| F[从变量读取] C -->|Cookie| G[浏览器自动发送] D --> H[构造 Authorization Header] E --> H F --> H G --> I[无需手动添加*] H --> J[发起 XHR/Fetch 请求] I --> J J --> K[设置 headers] K --> L[发送到服务器] style G fill:#ff9999 style H fill:#90EE90 style I fill:#90EE90 subgraph 注 G1[Cookie 方式需要 SameSite 属性] G2[防止 CSRF 攻击] end G1 -.-> G G2 -.-> G ``` **浏览器自动发送 Cookie,但需要手动添加 Bearer Token** ### 浏览器存储 Token 的方式 | 存储方式 | 特点 | 安全性 | 跨域访问 | |---------|------|--------|---------| | LocalStorage | 持久化,手动管理 | ❌ XSS 风险 | ✅ 支持 | | SessionStorage | 会话结束清除 | ❌ XSS 风险 | ❌ 仅同源 | | Memory | 页面刷新丢失 | ✅ 最安全 | ❌ 仅当前页面 | | Cookie | 可设置 HttpOnly | ✅ 防 XSS | ⚠️ 需 SameSite | ### 前端实现 (JavaScript) ```javascript // 1. 登录后存储 Token async function login(username, password) { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const { access_token } = await response.json(); localStorage.setItem('token', access_token); } // 2. 带认证头访问 API async function fetchProtectedResource() { const token = localStorage.getItem('token'); const response = await fetch('/api/resource', { headers: { 'Authorization': `Bearer ${token}` } }); if (response.status === 401) { // Token 过期,需要刷新或重新登录 handleTokenExpired(); } return response.json(); } // 3. Axios 拦截器自动添加 Token axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); ``` ## 加密与签名机制 ### JWT 签名算法对比 | 算法类型 | 算法 | 密钥类型 | 特点 | | ----- | ----- | ----- | ------------------- | | HMAC | HS256 | 对称密钥 | 服务器签发和验证都 uses 同一密钥 | | HMAC | HS512 | 对称密钥 | 更强的哈希,性能稍低 | | RSA | RS256 | 非对称密钥 | 私钥签名,公钥验证 | | RSA | RS512 | 非对称密钥 | 更强的签名算法 | | ECDSA | ES256 | 椭圆曲线 | 比更短但同样安全 | ### HMAC-SHA256 签名流程 ```mermaid graph TD A[原始数据] --> B[Base64URL 编码 Header] A --> C[Base64URL 编码 Payload] B --> D[拼接 Header.Payload] C --> D D --> E[HMAC-SHA256 拼接结果] E --> F[使用 Secret Key] F --> G[生成 256-bit 签名] G --> H[Base64URL 编码签名] H --> I[拼接 final JWT] I --> J[Header.Payload.Signature] style E fill:#ff9999 style F fill:#ff9999 style G fill:#lightblue ``` **签名 = HMAC-SHA256(Base64URL(Header) + "." + Base64URL(Payload), Secret)** ### Go 实现签名与验证 ```go package auth import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "strings" ) type JWTBuilder struct { secretKey []byte } // 生成签名 func (j *JWTBuilder) sign(header, payload string) string { data := strings.Join([]string{header, payload}, ".") h := hmac.New(sha256.New, j.secretKey) h.Write([]byte(data)) signature := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) return signature } // 验证签名 func (j *JWTBuilder) verify(token string) bool { parts := strings.Split(token, ".") if len(parts) != 3 { return false } header, payload, signature := parts[0], parts[1], parts[2] // 重新计算签名 expectedSignature := j.sign(header, payload) // 比较签名 (恒定时间比较,防止计时攻击) return hmac.Equal([]byte(signature), []byte(expectedSignature)) } ``` ## 服务器验证流程 ### 详细的验证步骤 ```go package middleware import ( "net/http" "encoding/base64" "encoding/json" "time" ) type Claims struct { UserID string `json:"user_id"` exp int64 `json:"exp"` iat int64 `json:"iat"` iss string `json:"iss"` aud string `json:"aud"` } func (m *AuthMiddleware) validateToken(token string) (*Claims, error) { // 步骤 1: 分割三部分 parts := strings.Split(token, ".") if len(parts) != 3 { return nil, fmt.Errorf("invalid token format") } // 步骤 2: 验证签名 if !m.verifySignature(token) { return nil, fmt.Errorf("invalid signature") } // 步骤 3: 解码 Payload payload, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return nil, fmt.Errorf("invalid payload encoding") } // 步骤 4: 解析 Claims var claims Claims if err := json.Unmarshal(payload, &claims); err != nil { return nil, fmt.Errorf("invalid payload json") } // 步骤 5: 验证过期时间 if time.Now().Unix() > claims.exp { return nil, fmt.Errorf("token expired") } // 步骤 6: 验证签发者 if claims.iss != "my-app" { return nil, fmt.Errorf("invalid issuer") } // 步骤 7: 验证受众 if claims.aud != "api-users" { return nil, fmt.Errorf("invalid audience") } return &claims, nil } // 验证签名 func (m *AuthMiddleware) verifySignature(token string) bool { parts := strings.Split(token, ".") signatureData := parts[0] + "." + parts[1] providedSignature := parts[2] // 计算期望的签名 expectedSignature := m.sign(signatureData) // 恒定时间比较 return hmac.Equal( []byte(providedSignature), []byte(expectedSignature), ) } ``` ### HTTP 中间件 ```go // AuthMiddleware 验证每个请求 func (m *AuthMiddleware) Handler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 1. 提取 Authorization 头 authHeader := r.Header.Get("Authorization") if authHeader == "" { http.Error(w, "Missing Authorization header", http.StatusUnauthorized) return } // 2. 解析 Bearer Token if !strings.HasPrefix(authHeader, "Bearer ") { http.Error(w, "Invalid authorization scheme", http.StatusUnauthorized) return } token := strings.TrimPrefix(authHeader, "Bearer ") // 3. 验证 Token claims, err := m.validateToken(token) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } // 4. 将用户信息存入 Context ctx := context.WithValue(r.Context(), "user_id", claims.UserID) ctx = context.WithValue(ctx, "claims", claims) // 5. 继续处理请求 next.ServeHTTP(w, r.WithContext(ctx)) }) } ``` ## 常见 Authorization Scheme ### Basic Auth(基础认证) ```mermaid sequenceDiagram participant B as Browser participant S as Server participant DB as Database B->>S: GET /resource S-->>B: 401 Unauthorized Note right of B: WWW-Authenticate: Basic realm="Secure Area" B->>B: 用户输入用户名密码 B->>B: Base64("username:password") B->>S: GET /resource Note right of B: Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l S->>B: 解码 Base64 S->>DB: 验证用户名密码 DB-->>S: 验证结果 S-->>B: 200 OK + 资源 ``` **问题**:每次请求都需要用户名密码,不安全,不推荐 ### Digest Auth(摘要认证) ```mermaid sequenceDiagram participant C as Client participant S as Server C->>S: GET /resource S-->>C: 401 Unauthorized Note right of C: WWW-Authenticate: Digest
realm="Protected",
nonce="xyz123",
qop="auth" C->>C: 计算 response
MD5(username:realm:password)
MD5(method:uri)
HA1 = MD5(username:realm:password)
HA2 = MD5(method:uri)
response = MD5(HA1:nonce:HA2) C->>S: GET /resource Note right of C: Authorization: Digest
username="user",
realm="Protected",
nonce="xyz123",
uri="/resource",
response="abc123" S->>S: 重新计算 response S-->>C: 200 OK ``` **优点**:密码不直接传输,更安全 **缺点**:实现复杂,需要多次往返 ### Bearer Token(持有者令牌) ✅ **现代 Web 应用的标准选择** ```http # 请求示例 GET /api/user/profile HTTP/1.1 Host: api.example.com Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIiwidXNlcm5hbWUiOiJqb2huIiwiZXhwIjoxNzI4MjQ4MDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c # 响应示例 HTTP/1.1 200 OK Content-Type: application/json { "user_id": "123", "username": "john", "email": "john@example.com" } ``` ## Refresh Token 机制 ### Access Token vs Refresh Token ```mermaid graph LR A[Access Token] --> B[有效期短
15-30分钟] A --> C[用于 API 访问] A --> D[存储在浏览器] E[Refresh Token] --> F[有效期长
数天至数周] E --> G[用于获取新 Access Token] E --> H[存储在 HttpOnly Cookie] style A fill:#90EE90 style E fill:#ffcc00 ``` ### Token 刷新流程 ```mermaid sequenceDiagram participant B as Browser participant S as Server Note over B,S: Access Token 过期 B->>S: POST /api/refresh Note right of B: Body: {refresh_token: "..."} S->>S: 验证 Refresh Token S->>S: 生成新的 Access Token S-->>B: {access_token: "new_jwt..."} Note over B,S: 使用新 Token 继续请求 B->>S: GET /api/resource Note right of B: Authorization: Bearer new_jwt... S-->>B: 200 OK ``` **Refresh Token 优势**: - ✅ Access Token 短期有效,减少泄露风险 - ✅ Refresh Token 可随时撤销 - ✅ 用户无感知自动续期 ## 安全最佳实践 ### Token 安全传输 | 措施 | 说明 | Go 实现 | |-----|------|---------| | **HTTPS 强制** | 防止 Token 被窃取 | RedirectHTTPS 中间件 | | **短期有效期** | 降低被滥用风险 | Token 15-30 分钟 | | **签名验证** | 防止 Token 被篡改 | HMAC/RSA 签名 | | **黑名单机制** | 主动撤销 Token | Redis 存储 revoked_tokens | ### 浏览器安全设置 ```javascript // ✅ 推荐:HttpOnly Cookie 存储 Refresh Token document.cookie = `refresh_token=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800`; // ⚠️ 谨慎:LocalStorage 存储 Access Token localStorage.setItem('access_token', accessToken); // 需要 XSS 防护 // ❌ 避免:明文传输 Token // 必须使用 HTTPS ``` ### CORS 和 SameSite ```mermaid graph TD A[前端应用
example.com] --> B[后端 API
api.example.com] B --> C[CORS 策略] C --> D[Access-Control-Allow-Origin: https://example.com] C --> E[Access-Control-Allow-Credentials: true] B --> F[SameSite Cookie] F --> G[Strict: 严格模式] F --> H[Lax: 放宽模式] style D fill:#90EE90 style G fill:#90EE90 ``` **Go CORS 配置**: ```go func setupCORS() *cors.Cors { return cors.New(cors.Options{ AllowedOrigins: []string{"https://example.com"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, AllowedHeaders: []string{"Authorization", "Content-Type"}, AllowCredentials: true, MaxAge: 3600, }) } ``` ## 相关笔记 - [[金山办公作业/Week05/用户认证]] - 认证的实现方式 - [[CS/NET/HTTPS]] - SSL/TLS 加密传输 - [[CS/DB/访问控制]] - 数据库层面权限