This repository has been archived on 2026-05-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
obsidian/金山办公作业/Week05/用户认证.md
T
2026-04-20 22:47:51 +08:00

439 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [go, auth, security, jwt, session, authorization, assignment]
create time: 2026-04-17
---
# Go 语言用户认证实现指南
## 概述
用户认证是 Web 应用的核心安全机制。本文介绍在 Go 中实现用户认证的常见模式和最佳实践。
## 认证方式流程
### Session-Based 认证
```mermaid
sequenceDiagram
participant C as Client
participant S as Server
participant DB as Database
C->>S: POST /login (username, password)
S->>DB: 验证凭证
DB-->>S: 用户信息
S->>S: 创建 Session
S-->>C: Set-Cookie: session_id=xxx
Note over C: 存储到浏览器
C->>S: GET /protected + Cookie
S->>DB: 根据 session_id 查询
DB-->>S: Session 数据
S-->>C: 返回受保护资源
```
### JWT Token 认证
```mermaid
sequenceDiagram
participant C as Client
participant S as Server
participant Token as JWT Token
C->>S: POST /login (credentials)
S->>S: 验证用户
S->>S: 生成 JWT
Token->>S: eyJhbGciOiJIUzI1NiIs...
S-->>C: {access_token: "...", refresh_token: "..."}
C->>S: GET /api + Authorization: Bearer {token}
S->>S: 验证 Token 签名和过期
S-->>C: 返回 API 数据
```
## 对比分析
| 特性 | Session | JWT |
|-----|--------|-----|
| 存储位置 | 服务器 | 客户端 |
| 状态性 | 有状态 | 无状态 |
| 分布式支持 | 需要共享机制 | 原生支持 |
| 可撤销性 | 容易 | 困难 |
| 适用场景 | 传统网页 | RESTful API |
## OAuth 2.0 流程
```mermaid
sequenceDiagram
participant U as User
participant A as App
participant P as Provider (Google/GitHub)
U->>A: 点击登录
A->>P: 重定向到授权页
U->>P: 同意授权
P->>A: 重定向 + code
A->>P: 用 code 换取 access_token
P-->>A: {access_token, refresh_token}
A->>P: 使用 token 获取用户信息
P-->>A: 用户信息
A-->>U: 登录成功
```
## Go 语言实现方案
### Session-Based 实现
#### 使用 gorilla/sessions
```go
package main
import (
"net/http"
"github.com/gorilla/sessions"
)
var store = sessions.NewCookieStore([]byte("secret-key"))
func loginHandler(w http.ResponseWriter, r *http.Request) {
// 验证用户凭证
if validateUser(r) {
session, _ := store.Get(r, "session-name")
session.Values["user_id"] = "123"
session.Values["authenticated"] = true
session.Save(r, w)
}
}
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next(w, r)
}
}
```
### JWT Implementation
#### 标准流程
```go
package authenticator
import (
"time"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
UserID string `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
type JWTAuthenticator struct {
secretKey []byte
}
func NewJWTAuthenticator(secretKey string) *JWTAuthenticator {
return &JWTAuthenticator{
secretKey: []byte(secretKey),
}
}
// 生成 Token
func (j *JWTAuthenticator) GenerateToken(userID, username string) (string, error) {
claims := Claims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.secretKey)
}
// 验证 Token
func (j *JWTAuthenticator) ValidateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return j.secretKey, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, jwt.ErrSignatureInvalid
}
// 中间件
func (j *JWTAuthenticator) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Missing authorization header", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
claims, err := j.ValidateToken(tokenString)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// 将用户信息存入上下文
ctx := context.WithValue(r.Context(), "user", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
```
### OAuth 2.0 集成
```go
package oauth
import (
"context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var (
googleOauthConfig = &oauth2.Config{
RedirectURL: "http://localhost:8080/callback",
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email",
},
Endpoint: google.Endpoint,
}
)
func HandleGoogleLogin(w http.ResponseWriter, r *http.Request) {
url := googleOauthConfig.AuthCodeURL("state-token")
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func HandleGoogleCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
token, err := googleOauthConfig.Exchange(context.Background(), code)
if err != nil {
http.Error(w, "Failed to exchange token", http.StatusBadRequest)
return
}
// 使用 token 获取用户信息
// ...
}
```
## JWT 结构
```mermaid
graph LR
A[JWT Token] --> B[Header]
A --> C[Payload]
A --> D[Signature]
B --> B1[alg: HS256]
B --> B2[typ: JWT]
C --> C1[user_id]
C --> C2[exp: 时间戳]
C --> C3[iat: 颁发时间]
D --> D1[Header + Payload]
D --> D2[Secret Key]
```
## Authorization(授权)
**认证 vs 授权**:
- **认证(Authentication)**:你是谁?解决身份验证问题
- **授权(Authorization)**:你能做什么?解决权限控制问题
### Authorization 头格式
| 方式 | 格式 | 示例 |
|-----|------|------|
| Bearer Token | `Bearer <token>` | `Bearer eyJhbGciOiJIUzI1NiIs...` |
| Basic Auth | `Basic <credentials>` | `Basic YWxhZGRpbjpvcGVuc2VzYW1l` |
| API Key | `ApiKey <key>` | `ApiKey abc123xyz` |
### 在 Go 中处理 Authorization
```go
// Bearer Token 解析
func parseBearerToken(header string) (string, error) {
parts := strings.SplitN(header, " ", 2)
if parts[0] != "Bearer" || len(parts) < 2 {
return "", errors.New("invalid authorization format")
}
return parts[1], nil
}
// Basic Auth 解析
func parseBasicAuth(header string) (username, password string, err error) {
parts := strings.SplitN(header, " ", 2)
if parts[0] != "Basic" || len(parts) < 2 {
return "", "", errors.New("invalid basic auth format")
}
decoded, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return "", "", err
}
credentials := string(decoded)
idx := strings.Index(credentials, ":")
if idx == -1 {
return "", "", errors.New("invalid credentials format")
}
return credentials[:idx], credentials[idx+1:], nil
}
```
**详细信息**:💡 详见 [[CS/NET/Authorization]] - Authorization 机制详解(RBAC、ABAC、策略引擎)
## 安全最佳实践
### 密码安全
```go
package auth
import "golang.org/x/crypto/bcrypt"
// 加密密码
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
// 验证密码
func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
```
### 密钥管理
- ✅ 使用环境变量存储密钥
- ✅ 使用专业密钥管理服务(HashiCorp Vault)
- ❌ 不将密钥写入代码库
- ❌ 不在日志中打印密钥
### HTTPS 强制
```go
func RedirectHTTPS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Scheme != "https" {
httpsURL := "https://" + r.Host + r.URL.RequestURI()
http.Redirect(w, r, httpsURL, http.StatusMovedPermanently)
return
}
next.ServeHTTP(w, r)
})
}
```
## 常见安全威胁与防护
### CSRF 攻击流程与防护
```mermaid
sequenceDiagram
participant A as 攻击者
participant U as 用户
participant S as 服务器
%%% 受害场景 %%%
U->>U: 已登录网站 S,持有 Cookie
A->>U: 诱导点击恶意链接
U->>S: POST /transfer (自动携带 Cookie)
S-->>U: 转账成功 ⚠️
%%% 防护方案 %%%
Note over S: CSRF Token 模式
U->>S: GET /form
S-->>U: HTML + CSRF Token
U->>S: POST + Token in data
S->>S: 验证 Token
S-->>U: 请求通过 ✓
```
### 威胁防护清单
| 威胁类型 | 防护措施 |
|---------|---------|
| SQL 注入 | 使用参数化查询、ORM |
| XSS 攻击 | 输入验证、输出编码 |
| CSRF 攻击 | CSRF Token、SameSite Cookie |
| 重放攻击 | Timestamp、Nonce 验证 |
| Token 窃取 | HTTPS、短期 Token、刷新机制 |
## 性能优化
### JWT 优化
```go
// 1. 缓存已验证 Token
type CacheAuthenticator struct {
jwt *JWTAuthenticator
cache *cache.Cache
}
// 2. 使用 Key ID 轮换密钥
type KeyRotation struct {
currentKey []byte
oldKeys [][]byte
}
// 3. 缩短 Token 生命周期,使用 Refresh Token
type TokenPair struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
```
## 推荐库
| 库名 | 用途 | 特点 |
|-----|------|------|
| gorilla/sessions | Session 管理 | 功能完善,支持多种存储 |
| golang-jwt/jwt | JWT 处理 | 轻量级,标准实现 |
| golang.org/x/oauth2 | OAuth 2.0 | 官方实现,支持主流平台 |
| golang.org/x/crypto | 密码加密 | 包含 bcrypt、scrypt 等算法 |
## 学习资源
- [JWT 官方文档](https://jwt.io/)
- [OWASP 认证备忘单](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
- [Go Web Examples - Authentication](https://gowebexamples.com/routes/)
## 相关笔记
- [[CS/NET/Authorization]] - Authorization 授权机制详解
- [[CS/NET/HTTPS]] - HTTPS 原理
- [[CS/OS/进程线程]] - 并发安全
- [[CS/DB/SQL安全]] - SQL 注入防护