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

11 KiB
Raw Blame History

tags, create time
tags create time
go
auth
security
jwt
session
authorization
assignment
2026-04-17

Go 语言用户认证实现指南

概述

用户认证是 Web 应用的核心安全机制。本文介绍在 Go 中实现用户认证的常见模式和最佳实践。

认证方式流程

Session-Based 认证

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 认证

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 流程

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

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

标准流程

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 集成

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 结构

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

// 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、策略引擎)

安全最佳实践

密码安全

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 强制

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 攻击流程与防护

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 优化

// 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 等算法

学习资源

相关笔记