Go: Go 中间件与 JWT 认证
最后更新:2026-08-26
中间件是 Go 微服务的"管道"——认证、限流、log、恢复,每个横切关注点都是一个中间件。
当你需要在每个 API 端点检查 JWT 认证、记录日志、限制访问频率时,中间件模式让你写一次,处处生效。
1. 你将学到
- JWT 生成与验证
- Access Token + Refresh Token 双令牌
- RBAC 角色权限控制
- 令牌桶限流
- Recovery / Logging / CORS middleware
- 中间件管线串联
2. 一个后端工程师的真实故事
(1) 痛点:每个路由手动检查 Token,忘了 3 次
Charlie 在 API 网关团队,负责所有端点的认证:
"老板要求所有 API 都要检查 JWT Token。我在每个 handler 里抄了 15 行 Token 验证代码。上线的第二天,我忘了在 /admin/users 里加认证——任何人可以直接删除用户。CTO 说'安全审计发现了一个严重漏洞'。"
// 坏代码:每个 handler 手动检查
func deleteUser(w http.ResponseWriter, r *http.Request) {
// 忘了写 Token 验证!严重安全漏洞
userID := r.PathValue("id")
deleteUserFromDB(userID)
}
func updateOrder(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
// 每次都抄 15 行验证代码
if !validateToken(token) {
http.Error(w, "unauthorized", 401)
return
}
// 业务逻辑...
}
(2) Go 的解法:认证中间件
// 好代码:认证中间件,一次实现处处生效
func AuthMiddleware(jwtSecret string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := extractToken(r)
claims, err := validateJWT(token, jwtSecret)
if err != nil {
writeError(w, http.StatusUnauthorized, "invalid token")
return
}
// 把用户信息注入 Context
ctx := context.WithValue(r.Context(), "user", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// 注册时只需要一个 Middleware
mux.Handle("POST /api/orders", AuthMiddleware(secret)(orderHandler))
(3) 收益:手动 vs 中间件
| 维度 | 手动检查 | 中间件模式 |
|---|---|---|
| 漏检风险 | 每新增一个路由都可能忘 | 零 |
| 代码重复 | 每个 handler 15 行 | 0 行(handler 只关心业务) |
| 修改逻辑 | 改 20 个 handler | 改 1 个中间件 |
| 单元测试 | 每个 handler 都要测认证 | 只测中间件一次 |
3. JWT 认证
▶ 示例:JWT 生成与验证
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
)
// ---------- JWT 手动实现(不依赖第三方库)----------
type JWTClaims struct {
UserID int `json:"user_id"`
Role string `json:"role"`
Permissions []string `json:"permissions"`
ExpiresAt int64 `json:"exp"`
}
type JWTHeader struct {
Alg string `json:"alg"`
Typ string `json:"typ"`
}
func base64Encode(data []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(data), "=")
}
func base64Decode(s string) ([]byte, error) {
// 补齐 padding
switch len(s) % 4 {
case 2:
s += "=="
case 3:
s += "="
}
return base64.URLEncoding.DecodeString(s)
}
func createJWT(claims JWTClaims, secret string) (string, error) {
header := JWTHeader{Alg: "HS256", Typ: "JWT"}
headerJSON, _ := json.Marshal(header)
claimsJSON, _ := json.Marshal(claims)
headerEnc := base64Encode(headerJSON)
claimsEnc := base64Encode(claimsJSON)
// 签名
message := headerEnc + "." + claimsEnc
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(message))
signature := base64Encode(mac.Sum(nil))
return message + "." + signature, nil
}
func validateJWT(token string, secret string) (*JWTClaims, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid token format")
}
// 验证签名
message := parts[0] + "." + parts[1]
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(message))
expectedSig := base64Encode(mac.Sum(nil))
if !hmac.Equal([]byte(parts[2]), []byte(expectedSig)) {
return nil, fmt.Errorf("invalid signature")
}
// 解析 claims
claimsJSON, err := base64Decode(parts[1])
if err != nil {
return nil, err
}
var claims JWTClaims
if err := json.Unmarshal(claimsJSON, &claims); err != nil {
return nil, err
}
// 检查过期
if time.Now().Unix() > claims.ExpiresAt {
return nil, fmt.Errorf("token expired")
}
return &claims, nil
}
func main() {
secret := "my-secret-key"
// 生成 Token
claims := JWTClaims{
UserID: 1,
Role: "admin",
ExpiresAt: time.Now().Add(1 * time.Hour).Unix(),
}
token, _ := createJWT(claims, secret)
fmt.Printf("JWT: %s\n", token)
// 验证 Token
validated, err := validateJWT(token, secret)
if err != nil {
fmt.Printf("验证失败: %v\n", err)
} else {
fmt.Printf("验证成功: user=%d, role=%s\n", validated.UserID, validated.Role)
}
}
github.com/golang-jwt/jwt/v5),不要手动实现。上述代码仅为说明 JWT 工作原理。第三方库处理了更多的边缘情况(如 token 类型、密钥轮换、标准 claims 验证)。
4. Access Token + Refresh Token
▶ 示例:双令牌认证
package main
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
)
// ---------- Token 服务 ----------
type TokenService struct {
secret string
refreshTTL time.Duration
// 存储 refresh token(生产环境用 Redis)
refreshTokens map[string]int // token → userID
mu sync.RWMutex
}
func NewTokenService(secret string) *TokenService {
return &TokenService{
secret: secret,
refreshTTL: 7 * 24 * time.Hour,
refreshTokens: make(map[string]int),
}
}
func (s *TokenService) GenerateTokens(userID int, role string) (accessToken, refreshToken string, err error) {
// Access Token(15 分钟过期)
accessClaims := JWTClaims{
UserID: userID,
Role: role,
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
}
accessToken, err = createJWT(accessClaims, s.secret)
if err != nil {
return "", "", err
}
// Refresh Token(随机字符串,7 天过期)
bytes := make([]byte, 32)
rand.Read(bytes)
refreshToken = hex.EncodeToString(bytes)
s.mu.Lock()
s.refreshTokens[refreshToken] = userID
s.mu.Unlock()
return accessToken, refreshToken, nil
}
func (s *TokenService) RefreshAccessToken(refreshToken string) (string, error) {
s.mu.RLock()
userID, exists := s.refreshTokens[refreshToken]
s.mu.RUnlock()
if !exists {
return "", fmt.Errorf("invalid refresh token")
}
// 生成新的 Access Token
claims := JWTClaims{
UserID: userID,
Role: "user",
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
}
return createJWT(claims, s.secret)
}
func (s *TokenService) RevokeRefreshToken(refreshToken string) {
s.mu.Lock()
delete(s.refreshTokens, refreshToken)
s.mu.Unlock()
}
// ---------- Auth Handler ----------
type AuthHandler struct {
tokenSvc *TokenService
}
type loginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
type tokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int64 `json:"expires_in"`
}
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
var req loginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON")
return
}
// 验证用户名密码(生产环境用 bcrypt)
if req.Username != "admin" || req.Password != "password" {
writeError(w, http.StatusUnauthorized, "invalid credentials")
return
}
accessToken, refreshToken, err := h.tokenSvc.GenerateTokens(1, "admin")
if err != nil {
writeError(w, http.StatusInternalServerError, "token generation failed")
return
}
writeJSON(w, http.StatusOK, tokenResponse{
AccessToken: accessToken,
RefreshToken: refreshToken,
TokenType: "Bearer",
ExpiresIn: 900, // 15 分钟
})
}
func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
var req struct {
RefreshToken string `json:"refresh_token"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON")
return
}
accessToken, err := h.tokenSvc.RefreshAccessToken(req.RefreshToken)
if err != nil {
writeError(w, http.StatusUnauthorized, "invalid refresh token")
return
}
writeJSON(w, http.StatusOK, map[string]string{"access_token": accessToken})
}
func main() {
tokenSvc := NewTokenService("my-secret-key")
handler := &AuthHandler{tokenSvc: tokenSvc}
mux := http.NewServeMux()
mux.HandleFunc("POST /login", handler.Login)
mux.HandleFunc("POST /refresh", handler.Refresh)
fmt.Println("Auth server starting on :8080")
http.ListenAndServe(":8080", mux)
}
sequenceDiagram
participant Client
participant API as API Gateway
participant Auth as Auth Service
Client->>API: POST /login (username, password)
API->>Auth: 验证凭据
Auth-->>API: access_token (15min) + refresh_token (7d)
API-->>Client: 返回双令牌
Client->>API: GET /orders (Bearer access_token)
API->>API: 验证 access_token
API-->>Client: 200 OK
Client->>API: GET /orders (access_token 过期)
API-->>Client: 401 Unauthorized
Client->>API: POST /refresh (refresh_token)
API->>Auth: 验证 refresh_token
Auth-->>API: 新的 access_token
API-->>Client: 返回新 access_token
5. RBAC 权限中间件
▶ 示例:RBAC 实现
package main
import (
"context"
"fmt"
"encoding/json"
"net/http"
)
type UserRole string
const (
RoleAdmin UserRole = "admin"
RoleUser UserRole = "user"
RoleGuest UserRole = "guest"
)
// 权限定义
var rolePermissions = map[UserRole][]string{
RoleAdmin: {"read:users", "write:users", "delete:users", "read:orders", "write:orders"},
RoleUser: {"read:orders", "write:orders"},
RoleGuest: {"read:products"},
}
type Claims struct {
UserID int
Role UserRole
}
// 从 Context 获取认证信息
func GetClaims(r *http.Request) *Claims {
claims, _ := r.Context().Value("claims").(*Claims)
return claims
}
// RBAC middleware
func RequirePermission(permission string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := GetClaims(r)
if claims == nil {
writeError(w, http.StatusUnauthorized, "not authenticated")
return
}
permissions, exists := rolePermissions[claims.Role]
if !exists {
writeError(w, http.StatusForbidden, "no permissions defined for role")
return
}
// 检查权限
hasPermission := false
for _, p := range permissions {
if p == permission {
hasPermission = true
break
}
}
if !hasPermission {
writeError(w, http.StatusForbidden, "insufficient permissions")
return
}
next.ServeHTTP(w, r)
})
}
}
// ---------- 路由注册 ----------
func setupRoutes() http.Handler {
mux := http.NewServeMux()
// 公开端点
mux.HandleFunc("POST /login", loginHandler)
// 需要认证的端点
mux.Handle("GET /api/orders", RequirePermission("read:orders")(authMiddleware(http.HandlerFunc(listOrders))))
mux.Handle("POST /api/orders", RequirePermission("write:orders")(authMiddleware(http.HandlerFunc(createOrder))))
mux.Handle("DELETE /api/users/{id}", RequirePermission("delete:users")(authMiddleware(http.HandlerFunc(deleteUser))))
return mux
}
func main() {
fmt.Println("RBAC server starting on :8080")
http.ListenAndServe(":8080", setupRoutes())
}
(1) 中间件管线
Request → Auth Middleware → RBAC Middleware → Handler
| |
验证 JWT Token 检查 Role 权限
注入 Claims 用户有权限?
| 层级 | middleware | 职责 |
|---|---|---|
| 1 | Recovery | 捕获 panic,返回 500 |
| 2 | Logging | 记录请求耗时 |
| 3 | CORS | 处理跨域 |
| 4 | Auth | 验证 JWT,注入用户信息 |
| 5 | RBAC | 检查角色权限 |
| 6 | RateLimit | 令牌桶限流 |
| 7 | Handler | 业务逻辑 |
6. 令牌桶限流
▶ 示例:令牌桶实现
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
type TokenBucket struct {
mu sync.Mutex
tokens float64
maxTokens float64
refillRate float64
lastRefill time.Time
}
func NewTokenBucket(rate float64, burst int) *TokenBucket {
return &TokenBucket{
tokens: float64(burst),
maxTokens: float64(burst),
refillRate: rate,
lastRefill: time.Now(),
}
}
func (tb *TokenBucket) Allow() bool {
tb.mu.Lock()
defer tb.mu.Unlock()
// Refill tokens
now := time.Now()
elapsed := now.Sub(tb.lastRefill).Seconds()
tb.tokens = minFloat(tb.tokens+elapsed*tb.refillRate, tb.maxTokens)
tb.lastRefill = now
// Take a token
if tb.tokens >= 1 {
tb.tokens--
return true
}
return false
}
func minFloat(a, b float64) float64 {
if a < b {
return a
}
return b
}
// IP 限流器
type IPRateLimiter struct {
mu sync.RWMutex
buckets map[string]*TokenBucket
rate float64
burst int
}
func NewIPRateLimiter(rate float64, burst int) *IPRateLimiter {
return &IPRateLimiter{
buckets: make(map[string]*TokenBucket),
rate: rate,
burst: burst,
}
}
func (rl *IPRateLimiter) GetBucket(ip string) *TokenBucket {
rl.mu.Lock()
defer rl.mu.Unlock()
bucket, exists := rl.buckets[ip]
if !exists {
bucket = NewTokenBucket(rl.rate, rl.burst)
rl.buckets[ip] = bucket
}
return bucket
}
// RateLimiterMiddleware
func RateLimiterMiddleware(rate float64, burst int) Middleware {
limiter := NewIPRateLimiter(rate, burst)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
bucket := limiter.GetBucket(ip)
if !bucket.Allow() {
w.Header().Set("Retry-After", "1")
writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
return
}
next.ServeHTTP(w, r)
})
}
}
func main() {
limiter := RateLimiterMiddleware(10, 5)
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "OK")
})
fmt.Println("Rate-limited server on :8080")
http.ListenAndServe(":8080", limiter(mux))
}
7. 完整示例:API 网关中间件管线
// api_gateway.go
package main
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)
// ---------- 类型定义 ----------
type Middleware func(http.Handler) http.Handler
type Claims struct {
UserID int
Role string
}
// ---------- JWT 验证 ----------
func validateJWT(token string, secret string) (*Claims, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid token format")
}
message := parts[0] + "." + parts[1]
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(message))
expectedSig := strings.TrimRight(base64.URLEncoding.EncodeToString(mac.Sum(nil)), "=")
if !hmac.Equal([]byte(parts[2]), []byte(expectedSig)) {
return nil, fmt.Errorf("invalid signature")
}
payload, err := base64.URLEncoding.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("invalid payload")
}
var claims Claims
if err := json.Unmarshal(payload, &claims); err != nil {
return nil, err
}
return &claims, nil
}
// ---------- 令牌桶限流 ----------
type TokenBucket struct {
mu sync.Mutex
tokens float64
maxTokens float64
refillRate float64
lastRefill time.Time
}
func NewTokenBucket(rate float64, burst int) *TokenBucket {
return &TokenBucket{
tokens: float64(burst),
maxTokens: float64(burst),
refillRate: rate,
lastRefill: time.Now(),
}
}
func (tb *TokenBucket) Allow() bool {
tb.mu.Lock()
defer tb.mu.Unlock()
now := time.Now()
elapsed := now.Sub(tb.lastRefill).Seconds()
tb.tokens = minFloat(tb.tokens+elapsed*tb.refillRate, tb.maxTokens)
tb.lastRefill = now
if tb.tokens >= 1 {
tb.tokens--
return true
}
return false
}
type IPRateLimiter struct {
mu sync.RWMutex
buckets map[string]*TokenBucket
rate float64
burst int
}
func NewIPRateLimiter(rate float64, burst int) *IPRateLimiter {
return &IPRateLimiter{
buckets: make(map[string]*TokenBucket),
rate: rate,
burst: burst,
}
}
func (rl *IPRateLimiter) GetBucket(ip string) *TokenBucket {
rl.mu.Lock()
defer rl.mu.Unlock()
bucket, exists := rl.buckets[ip]
if !exists {
bucket = NewTokenBucket(rl.rate, rl.burst)
rl.buckets[ip] = bucket
}
return bucket
}
// ---------- middleware ----------
// Recovery
func Recovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("[PANIC] %v", err)
writeError(w, http.StatusInternalServerError, "internal error")
}
}()
next.ServeHTTP(w, r)
})
}
// Logging
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
next.ServeHTTP(w, r)
log.Printf("[%s] %s → %v", r.Method, r.URL.Path, time.Since(start))
})
}
// CORS
func CORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
// Auth(JWT 认证)
func Auth(secret string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "Bearer ") {
writeError(w, http.StatusUnauthorized, "missing token")
return
}
token := authHeader[7:]
claims, err := validateJWT(token, secret)
if err != nil {
writeError(w, http.StatusUnauthorized, err.Error())
return
}
ctx := context.WithValue(r.Context(), "claims", &Claims{
UserID: claims.UserID,
Role: claims.Role,
})
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// RBAC
func RequireRole(roles ...string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims, ok := r.Context().Value("claims").(*Claims)
if !ok {
writeError(w, http.StatusUnauthorized, "not authenticated")
return
}
for _, role := range roles {
if claims.Role == role {
next.ServeHTTP(w, r)
return
}
}
writeError(w, http.StatusForbidden, "insufficient permissions")
})
}
}
// Rate Limiter
var rateLimiter = NewIPRateLimiter(10, 20) // 10 req/s, burst 20
func RateLimit(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
bucket := rateLimiter.GetBucket(ip)
if !bucket.Allow() {
writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
return
}
next.ServeHTTP(w, r)
})
}
// ---------- Chain ----------
func Chain(h http.Handler, mws ...Middleware) http.Handler {
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h)
}
return h
}
// ---------- Handlers ----------
type OrderHandler struct{}
func (h *OrderHandler) ListOrders(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"orders": "list"})
}
func (h *OrderHandler) CreateOrder(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, map[string]string{"order": "created"})
}
type AdminHandler struct{}
func (h *AdminHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"deleted": "user"})
}
// ---------- 工具函数 ----------
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, msg string) {
writeJSON(w, status, map[string]string{"error": msg})
}
// ---------- Main ----------
func main() {
jwtSecret := "super-secret-key"
orders := &OrderHandler{}
mux := http.NewServeMux()
// 公开端点
mux.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"token": "login-implementation"})
})
// 认证 + 限流的端点
mux.Handle("GET /api/orders", Chain(
http.HandlerFunc(orders.ListOrders),
Auth(jwtSecret),
RateLimit,
))
mux.Handle("POST /api/orders", Chain(
http.HandlerFunc(orders.CreateOrder),
Auth(jwtSecret),
RateLimit,
))
// 管理员端点(Auth + RBAC + RateLimit)
mux.Handle("DELETE /api/users/{id}", Chain(
http.HandlerFunc((&AdminHandler{}).DeleteUser),
Auth(jwtSecret),
RequireRole("admin"),
RateLimit,
))
// 全局中间件(Recovery → Logging → CORS → route)
app := Chain(mux, Recovery, Logging, CORS)
server := &http.Server{Addr: ":8080", Handler: app}
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Println("关闭中...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(ctx)
}()
log.Println("API 网关启动于 :8080")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}
❓ 常见问题
github.com/golang-jwt/jwt/v5。Access Token 短期(15 分钟),Refresh Token 长期(7 天)存储在服务端。rolePermissions[role] = []permission + 中间件检查 permission in rolePermissions[claims.Role]。debug.Stack() 打印堆栈。日志级别用 ERROR。生产环境不要返回堆栈给客户端——只记录在服务端日志中。Chain(h, A, B, C) → 请求经过 A→B→C→Handler→C→B→A。注册顺序 = 外层到内层。Recovery 在最外层(捕获任何层抛出的 panic),Handler 在最内层(业务逻辑)。📖 小节
- JWT:Header + Payload + Signature,
golang-jwt/jwt/v5 - 双令牌:Access(15min) + Refresh(7d)
- Auth 中间件:验证 Token,注入 Claims 到 Context
- RBAC:角色 → 权限映射,中间件检查权限
- 令牌桶限流:突发允许,平均控制
- Recovery 中间件:捕获 panic,返回 500
- 中间件顺序:Recovery → Logging → CORS → Auth → RBAC → RateLimit → Handler
📝 作业
-
基础题(难度⭐):实现一个简单的认证中间件。创建一个
/api/protected端点,要求请求头包含Authorization: Bearer token123。Token 正确返回{"message": "access granted"},错误返回 401。 -
进阶题(难度⭐⭐):实现一个完整的 JWT 认证系统。要求:(1)
POST /register注册(bcrypt 加密密码);(2)POST /login登录(返回 access_token + refresh_token);(3) Auth 中间件验证所有/api/*端点的 access_token;(4)POST /refresh用 refresh_token 换新 access_token;(5)POST /logout撤销 refresh_token。 -
挑战题(难度⭐⭐⭐):实现一个带 RBAC 和限流的博客管理 API。要求:(1) 三种角色:admin/editor/reader;(2) admin:所有权限;(3) editor:创建/编辑/删除自己的文章;(4) reader:只能阅读;(5) 每个角色有不同的速率限制;(6) 所有端点通过中间件管线:Recovery → Logging → Auth → RBAC → RateLimit → Handler;(7) 用
-race验证并发安全。