Token 管理工具:从设计到实现的完整指南

一、项目背景与需求分析

在现代Web应用和微服务架构中,Token(令牌)已成为身份认证和授权的主流方案。无论是JWT、OAuth2.0还是自定义Token,都需要一套完整的管理机制来确保系统的安全性和可用性。然而,许多项目在Token管理方面存在以下痛点:

常见问题

  • Token生成和验证逻辑分散在各个服务中,难以统一维护
  • 缺乏统一的Token存储和刷新机制,导致安全漏洞
  • 多端登录、单点登录等复杂场景处理不当
  • Token过期、续签、注销等生命周期管理不完善
  • 缺乏监控和审计能力,无法追踪Token使用情况

核心需求

  • 统一的Token生成、验证、刷新、注销接口
  • 支持多种Token类型(JWT、Opaque Token等)
  • 可扩展的存储方案(Redis、数据库、内存)
  • 多端登录和单点登录支持
  • Token生命周期管理(过期、续签、强制失效)
  • 安全审计和监控能力
  • 高性能和高可用性

二、技术选型与架构设计

2.1 技术栈选择

核心框架

  • Go语言:高性能、并发能力强,适合高并发Token管理场景
  • Gin框架:轻量级Web框架,性能优异,中间件生态丰富
  • GORM:ORM框架,支持多种数据库,简化数据操作

存储方案

  • Redis:作为Token缓存,支持高并发读写和过期机制
  • MySQL/PostgreSQL:持久化存储Token元数据和审计日志

安全组件

  • JWT库:go-jwt库,支持多种签名算法
  • 加密库:bcrypt用于密码加密,crypto用于Token签名

其他依赖

  • Viper:配置管理
  • Zap:高性能日志库
  • Prometheus:监控指标暴露
  • Docker:容器化部署

2.2 系统架构设计

┌─────────────────┐    ┌─────────────────┐
│    客户端       │    │    Token管理服务 │
│                 │    │                 │
│  ┌───────────┐  │    │  ┌───────────┐  │
│  │  应用     │  │    │  │  API接口层 │  │
│  └───────────┘  │    │  └───────────┘  │
│        │        │    │        │        │
│  ┌───────────┐  │    │  ┌───────────┐  │
│  │ Token管理 │  │    │  │ 业务逻辑层 │  │
│  │ 客户端SDK │  │    │  └───────────┘  │
│  └───────────┘  │    │        │        │
└────────│─────────┘    │  ┌───────────┐  │
         │              │  │ 数据访问层 │  │
         └──────────────┼─▶└───────────┘  │
                        │        │        │
                        │  ┌───────────┐  │
                        │  │  存储层    │  │
                        │  │ (Redis/DB) │  │
                        │  └───────────┘  │
                        └─────────────────┘

架构说明

  • 客户端SDK:封装Token管理逻辑,提供统一的API调用方式
  • API接口层:提供RESTful API,处理HTTP请求和响应
  • 业务逻辑层:实现Token的生成、验证、刷新、注销等核心逻辑
  • 数据访问层:抽象存储接口,支持多种存储方案
  • 存储层:Redis用于Token缓存,数据库用于持久化存储

三、核心功能实现

3.1 Token实体定义

// TokenType Token类型
type TokenType string

const (
    TokenTypeAccess  TokenType = "access"  // 访问令牌
    TokenTypeRefresh TokenType = "refresh" // 刷新令牌
    TokenTypeAPI     TokenType = "api"      // API令牌
)

// Token 令牌实体
type Token struct {
    ID           string    `json:"id" gorm:"primaryKey;type:varchar(64)"`           // Token唯一标识
    UserID       string    `json:"user_id" gorm:"type:varchar(64);index"`           // 用户ID
    ClientID     string    `json:"client_id" gorm:"type:varchar(64);index"`          // 客户端ID
    TokenType    TokenType `json:"token_type" gorm:"type:varchar(20)"`              // Token类型
    TokenValue   string    `json:"token_value" gorm:"type:text;uniqueIndex"`       // Token值
    ExpiresAt    time.Time `json:"expires_at" gorm:"index"`                         // 过期时间
    RefreshToken string    `json:"refresh_token" gorm:"type:varchar(256);index"`     // 刷新令牌
    RefreshExp   time.Time `json:"refresh_exp" gorm:"index"`                         // 刷新令牌过期时间
    Scope        string    `json:"scope" gorm:"type:varchar(512)"`                    // 权限范围
    Metadata     string    `json:"metadata" gorm:"type:json"`                        // 元数据
    CreatedAt    time.Time `json:"created_at" gorm:"autoCreateTime"`                  // 创建时间
    UpdatedAt    time.Time `json:"updated_at" gorm:"autoUpdateTime"`                 // 更新时间
}

// TokenClaims JWT Claims
type TokenClaims struct {
    jwx.StandardClaims
    UserID   string    `json:"user_id"`
    ClientID string    `json:"client_id"`
    Scope    string    `json:"scope"`
    Metadata string    `json:"metadata"`
}

3.2 Token生成服务

// TokenService Token服务接口
type TokenService interface {
    // GenerateAccessToken 生成访问令牌
    GenerateAccessToken(userID, clientID string, scope []string, metadata map[string]interface{}) (*Token, error)
    
    // GenerateRefreshToken 生成刷新令牌
    GenerateRefreshToken(userID, clientID string) (*Token, error)
    
    // VerifyToken 验证令牌
    VerifyToken(tokenString string) (*TokenClaims, error)
    
    // RefreshToken 刷新令牌
    RefreshToken(refreshToken string) (*Token, error)
    
    // RevokeToken 撤销令牌
    RevokeToken(tokenID string) error
    
    // RevokeUserTokens 撤销用户所有令牌
    RevokeUserTokens(userID string) error
    
    // GetTokenByID 根据ID获取令牌
    GetTokenByID(tokenID string) (*Token, error)
    
    // ListTokens 列出令牌
    ListTokens(userID, clientID string, page, size int) ([]*Token, int64, error)
}

// tokenServiceImpl Token服务实现
type tokenServiceImpl struct {
    config       *Config
    tokenRepo    repository.TokenRepository
    redisClient  *redis.Client
    jwtSigner    jwx.Signer
    jwtVerifier  jwx.Verifier
}

// NewTokenService 创建Token服务实例
func NewTokenService(config *Config, tokenRepo repository.TokenRepository, redisClient *redis.Client) (TokenService, error) {
    // 创建JWT签名器
    key, err := jwx.NewJWKFromBytes([]byte(config.JWTSecret))
    if err != nil {
        return nil, fmt.Errorf("failed to create JWK: %v", err)
    }
    
    signer, err := jwx.NewSigner(jwx.WithKey(jwx.AlgorithmHS256, key))
    if err != nil {
        return nil, fmt.Errorf("failed to create signer: %v", err)
    }
    
    verifier, err := jwx.NewVerifier(jwx.WithKey(jwx.AlgorithmHS256, key))
    if err != nil {
        return nil, fmt.Errorf("failed to create verifier: %v", err)
    }
    
    return &tokenServiceImpl{
        config:      config,
        tokenRepo:   tokenRepo,
        redisClient: redisClient,
        jwtSigner:   signer,
        jwtVerifier: verifier,
    }, nil
}

// GenerateAccessToken 生成访问令牌
func (s *tokenServiceImpl) GenerateAccessToken(userID, clientID string, scope []string, metadata map[string]interface{}) (*Token, error) {
    // 生成Token ID
    tokenID := uuid.New().String()
    
    // 设置过期时间
    expiresAt := time.Now().Add(s.config.AccessTokenExpiry)
    
    // 构建Claims
    claims := &TokenClaims{
        StandardClaims: jwx.StandardClaims{
            ID:        tokenID,
            Subject:   userID,
            IssuedAt:  time.Now().Unix(),
            ExpiresAt: expiresAt.Unix(),
            Issuer:    s.config.Issuer,
        },
        UserID:   userID,
        ClientID: clientID,
        Scope:    strings.Join(scope, " "),
    }
    
    // 序列化元数据
    if metadata != nil {
        metadataBytes, err := json.Marshal(metadata)
        if err != nil {
            return nil, fmt.Errorf("failed to marshal metadata: %v", err)
        }
        claims.Metadata = string(metadataBytes)
    }
    
    // 生成JWT Token
    tokenBytes, err := jwx.Sign(claims, s.jwtSigner)
    if err != nil {
        return nil, fmt.Errorf("failed to sign token: %v", err)
    }
    tokenString := string(tokenBytes)
    
    // 生成刷新令牌
    refreshToken, err := s.GenerateRefreshToken(userID, clientID)
    if err != nil {
        return nil, err
    }
    
    // 创建Token实体
    token := &Token{
        ID:           tokenID,
        UserID:       userID,
        ClientID:     clientID,
        TokenType:    TokenTypeAccess,
        TokenValue:   tokenString,
        ExpiresAt:    expiresAt,
        RefreshToken: refreshToken.TokenValue,
        RefreshExp:   refreshToken.ExpiresAt,
        Scope:        strings.Join(scope, " "),
        Metadata:     claims.Metadata,
        CreatedAt:    time.Now(),
        UpdatedAt:    time.Now(),
    }
    
    // 保存到数据库
    if err := s.tokenRepo.Create(token); err != nil {
        return nil, fmt.Errorf("failed to save token to database: %v", err)
    }
    
    // 保存到Redis缓存
    if err := s.saveTokenToRedis(token); err != nil {
        log.Warnf("failed to save token to redis: %v", err)
    }
    
    return token, nil
}

// GenerateRefreshToken 生成刷新令牌
func (s *tokenServiceImpl) GenerateRefreshToken(userID, clientID string) (*Token, error) {
    tokenID := uuid.New().String()
    expiresAt := time.Now().Add(s.config.RefreshTokenExpiry)
    
    // 刷新令牌使用不透明Token,不包含Claims
    tokenValue := generateOpaqueToken()
    
    token := &Token{
        ID:         tokenID,
        UserID:     userID,
        ClientID:   clientID,
        TokenType:  TokenTypeRefresh,
        TokenValue: tokenValue,
        ExpiresAt:  expiresAt,
        CreatedAt:  time.Now(),
        UpdatedAt:  time.Now(),
    }
    
    if err := s.tokenRepo.Create(token); err != nil {
        return nil, fmt.Errorf("failed to save refresh token: %v", err)
    }
    
    if err := s.saveTokenToRedis(token); err != nil {
        log.Warnf("failed to save refresh token to redis: %v", err)
    }
    
    return token, nil
}

// generateOpaqueToken 生成不透明Token
func generateOpaqueToken() string {
    b := make([]byte, 32)
    if _, err := rand.Read(b); err != nil {
        panic(err)
    }
    return base64.URLEncoding.EncodeToString(b)
}

// saveTokenToRedis 保存Token到Redis
func (s *tokenServiceImpl) saveTokenToRedis(token *Token) error {
    ctx := context.Background()
    
    // 序列化Token
    tokenBytes, err := json.Marshal(token)
    if err != nil {
        return fmt.Errorf("failed to marshal token: %v", err)
    }
    
    // 计算过期时间
    expiry := time.Until(token.ExpiresAt)
    if expiry < 0 {
        return fmt.Errorf("token already expired")
    }
    
    // 保存到Redis
    if err := s.redisClient.Set(ctx, buildTokenKey(token.TokenValue), tokenBytes, expiry).Err(); err != nil {
        return fmt.Errorf("failed to set token to redis: %v", err)
    }
    
    return nil
}

// buildTokenKey 构建Redis Key
func buildTokenKey(tokenValue string) string {
    return fmt.Sprintf("token:%s", tokenValue)
}

3.3 Token验证服务

// VerifyToken 验证令牌
func (s *tokenServiceImpl) VerifyToken(tokenString string) (*TokenClaims, error) {
    // 首先从Redis缓存中查找
    if claims, err := s.verifyTokenFromRedis(tokenString); err == nil {
        return claims, nil
    }
    
    // 如果Redis中没有,从数据库查找并验证
    token, err := s.tokenRepo.FindByTokenValue(tokenString)
    if err != nil {
        return nil, fmt.Errorf("token not found: %v", err)
    }
    
    // 检查Token是否过期
    if time.Now().After(token.ExpiresAt) {
        return nil, fmt.Errorf("token expired")
    }
    
    // 验证JWT签名
    claims := &TokenClaims{}
    if err := jwx.Verify([]byte(tokenString), s.jwtVerifier, claims); err != nil {
        return nil, fmt.Errorf("invalid token signature: %v", err)
    }
    
    // 验证Token ID是否匹配
    if claims.ID != token.ID {
        return nil, fmt.Errorf("token id mismatch")
    }
    
    // 保存到Redis缓存
    if err := s.saveTokenToRedis(token); err != nil {
        log.Warnf("failed to save token to redis: %v", err)
    }
    
    return claims, nil
}

// verifyTokenFromRedis 从Redis验证令牌
func (s *tokenServiceImpl) verifyTokenFromRedis(tokenString string) (*TokenClaims, error) {
    ctx := context.Background()
    
    // 从Redis获取Token
    tokenBytes, err := s.redisClient.Get(ctx, buildTokenKey(tokenString)).Bytes()
    if err != nil {
        if err == redis.Nil {
            return nil, fmt.Errorf("token not found in redis")
        }
        return nil, fmt.Errorf("failed to get token from redis: %v", err)
    }
    
    // 反序列化Token
    var token Token
    if err := json.Unmarshal(tokenBytes, &token); err != nil {
        return nil, fmt.Errorf("failed to unmarshal token: %v", err)
    }
    
    // 检查Token是否过期
    if time.Now().After(token.ExpiresAt) {
        // 从Redis删除过期Token
        s.redisClient.Del(ctx, buildTokenKey(tokenString))
        return nil, fmt.Errorf("token expired")
    }
    
    // 解析Claims
    claims := &TokenClaims{}
    if err := jwx.ParseClaims([]byte(tokenString), claims); err != nil {
        return nil, fmt.Errorf("failed to parse claims: %v", err)
    }
    
    return claims, nil
}

3.4 Token刷新服务

// RefreshToken 刷新令牌
func (s *tokenServiceImpl) RefreshToken(refreshToken string) (*Token, error) {
    // 验证刷新令牌
    token, err := s.tokenRepo.FindByTokenValue(refreshToken)
    if err != nil {
        return nil, fmt.Errorf("refresh token not found: %v", err)
    }
    
    // 检查刷新令牌是否过期
    if time.Now().After(token.ExpiresAt) {
        return nil, fmt.Errorf("refresh token expired")
    }
    
    // 检查刷新令牌类型
    if token.TokenType != TokenTypeRefresh {
        return nil, fmt.Errorf("invalid refresh token type")
    }
    
    // 撤销旧的访问令牌
    if err := s.revokeUserAccessTokens(token.UserID, token.ClientID); err != nil {
        return nil, fmt.Errorf("failed to revoke old tokens: %v", err)
    }
    
    // 解析原访问令牌的Scope
    var scope []string
    if token.Scope != "" {
        scope = strings.Split(token.Scope, " ")
    }
    
    // 解析元数据
    var metadata map[string]interface{}
    if token.Metadata != "" {
        if err := json.Unmarshal([]byte(token.Metadata), &metadata); err != nil {
            return nil, fmt.Errorf("failed to unmarshal metadata: %v", err)
        }
    }
    
    // 生成新的访问令牌
    newAccessToken, err := s.GenerateAccessToken(token.UserID, token.ClientID, scope, metadata)
    if err != nil {
        return nil, fmt.Errorf("failed to generate new access token: %v", err)
    }
    
    // 撤销旧的刷新令牌
    if err := s.RevokeToken(token.ID); err != nil {
        return nil, fmt.Errorf("failed to revoke old refresh token: %v", err)
    }
    
    return newAccessToken, nil
}

// revokeUserAccessTokens 撤销用户的所有访问令牌
func (s *tokenServiceImpl) revokeUserAccessTokens(userID, clientID string) error {
    tokens, err := s.tokenRepo.FindByUserIDAndClientID(userID, clientID, TokenTypeAccess)
    if err != nil {
        return fmt.Errorf("failed to find user tokens: %v", err)
    }
    
    for _, token := range tokens {
        if err := s.RevokeToken(token.ID); err != nil {
            log.Warnf("failed to revoke token %s: %v", token.ID, err)
        }
    }
    
    return nil
}

3.5 Token撤销服务

// RevokeToken 撤销令牌
func (s *tokenServiceImpl) RevokeToken(tokenID string) error {
    // 从数据库查找Token
    token, err := s.tokenRepo.FindByID(tokenID)
    if err != nil {
        return fmt.Errorf("token not found: %v", err)
    }
    
    // 从数据库删除
    if err := s.tokenRepo.Delete(tokenID); err != nil {
        return fmt.Errorf("failed to delete token from database: %v", err)
    }
    
    // 从Redis删除
    ctx := context.Background()
    if err := s.redisClient.Del(ctx, buildTokenKey(token.TokenValue)).Err(); err != nil {
        log.Warnf("failed to delete token from redis: %v", err)
    }
    
    return nil
}

// RevokeUserTokens 撤销用户所有令牌
func (s *tokenServiceImpl) RevokeUserTokens(userID string) error {
    // 查找用户的所有Token
    tokens, err := s.tokenRepo.FindByUserID(userID)
    if err != nil {
        return fmt.Errorf("failed to find user tokens: %v", err)
    }
    
    // 批量删除
    for _, token := range tokens {
        if err := s.RevokeToken(token.ID); err != nil {
            log.Warnf("failed to revoke token %s: %v", token.ID, err)
        }
    }
    
    return nil
}

四、数据访问层实现

4.1 存储接口定义

// TokenRepository Token存储接口
type TokenRepository interface {
    // Create 创建Token
    Create(token *Token) error
    
    // FindByID 根据ID查找Token
    FindByID(id string) (*Token, error)
    
    // FindByTokenValue 根据Token值查找
    FindByTokenValue(tokenValue string) (*Token, error)
    
    // FindByUserID 根据用户ID查找
    FindByUserID(userID string) ([]*Token, error)
    
    // FindByUserIDAndClientID 根据用户ID和客户端ID查找
    FindByUserIDAndClientID(userID, clientID string, tokenType TokenType) ([]*Token, error)
    
    // Delete 删除Token
    Delete(id string) error
    
    // DeleteExpired 删除过期Token
    DeleteExpired() error
    
    // List 列出Token
    List(page, size int) ([]*Token, int64, error)
}

// mysqlTokenRepository MySQL实现
type mysqlTokenRepository struct {
    db *gorm.DB
}

// NewMySQLTokenRepository 创建MySQL存储实例
func NewMySQLTokenRepository(db *gorm.DB) TokenRepository {
    return &mysqlTokenRepository{db: db}
}

// Create 创建Token
func (r *mysqlTokenRepository) Create(token *Token) error {
    return r.db.Create(token).Error
}

// FindByID 根据ID查找Token
func (r *mysqlTokenRepository) FindByID(id string) (*Token, error) {
    var token Token
    if err := r.db.Where("id = ?", id).First(&token).Error; err != nil {
        return nil, err
    }
    return &token, nil
}

// FindByTokenValue 根据Token值查找
func (r *mysqlTokenRepository) FindByTokenValue(tokenValue string) (*Token, error) {
    var token Token
    if err := r.db.Where("token_value = ?", tokenValue).First(&token).Error; err != nil {
        return nil, err
    }
    return &token, nil
}

// FindByUserID 根据用户ID查找
func (r *mysqlTokenRepository) FindByUserID(userID string) ([]*Token, error) {
    var tokens []*Token
    if err := r.db.Where("user_id = ?", userID).Find(&tokens).Error; err != nil {
        return nil, err
    }
    return tokens, nil
}

// FindByUserIDAndClientID 根据用户ID和客户端ID查找
func (r *mysqlTokenRepository) FindByUserIDAndClientID(userID, clientID string, tokenType TokenType) ([]*Token, error) {
    var tokens []*Token
    query := r.db.Where("user_id = ? AND client_id = ?", userID, clientID)
    if tokenType != "" {
        query = query.Where("token_type = ?", tokenType)
    }
    if err := query.Find(&tokens).Error; err != nil {
        return nil, err
    }
    return tokens, nil
}

// Delete 删除Token
func (r *mysqlTokenRepository) Delete(id string) error {
    return r.db.Where("id = ?", id).Delete(&Token{}).Error
}

// DeleteExpired 删除过期Token
func (r *mysqlTokenRepository) DeleteExpired() error {
    return r.db.Where("expires_at < ?", time.Now()).Delete(&Token{}).Error
}

// List 列出Token
func (r *mysqlTokenRepository) List(page, size int) ([]*Token, int64, error) {
    var tokens []*Token
    var total int64
    
    offset := (page - 1) * size
    if err := r.db.Model(&Token{}).Count(&total).Offset(offset).Limit(size).Find(&tokens).Error; err != nil {
        return nil, 0, err
    }
    
    return tokens, total, nil
}

五、API接口层实现

5.1 HTTP接口定义

// TokenHandler Token HTTP处理器
type TokenHandler struct {
    tokenService service.TokenService
}

// NewTokenHandler 创建Token处理器
func NewTokenHandler(tokenService service.TokenService) *TokenHandler {
    return &TokenHandler{tokenService: tokenService}
}

// GenerateTokenRequest 生成Token请求
type GenerateTokenRequest struct {
    UserID   string                   `json:"user_id" binding:"required"`
    ClientID string                   `json:"client_id" binding:"required"`
    Scope    []string                 `json:"scope"`
    Metadata map[string]interface{}   `json:"metadata"`
}

// GenerateTokenResponse 生成Token响应
type GenerateTokenResponse struct {
    AccessToken  string    `json:"access_token"`
    TokenType    string    `json:"token_type"`
    ExpiresIn    int64     `json:"expires_in"`
    RefreshToken string    `json:"refresh_token"`
    Scope        string    `json:"scope"`
}

// RefreshTokenRequest 刷新Token请求
type RefreshTokenRequest struct {
    RefreshToken string `json:"refresh_token" binding:"required"`
}

// VerifyTokenRequest 验证Token请求
type VerifyTokenRequest struct {
    Token string `json:"token" binding:"required"`
}

// VerifyTokenResponse 验证Token响应
type VerifyTokenResponse struct {
    Valid   bool          `json:"valid"`
    Claims  *TokenClaims  `json:"claims,omitempty"`
    Message string        `json:"message,omitempty"`
}

// GenerateToken 生成Token
func (h *TokenHandler) GenerateToken(c *gin.Context) {
    var req GenerateTokenRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    token, err := h.tokenService.GenerateAccessToken(req.UserID, req.ClientID, req.Scope, req.Metadata)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    resp := GenerateTokenResponse{
        AccessToken:  token.TokenValue,
        TokenType:    "Bearer",
        ExpiresIn:    int64(time.Until(token.ExpiresAt).Seconds()),
        RefreshToken: token.RefreshToken,
        Scope:        token.Scope,
    }
    
    c.JSON(http.StatusOK, resp)
}

// RefreshToken 刷新Token
func (h *TokenHandler) RefreshToken(c *gin.Context) {
    var req RefreshTokenRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    token, err := h.tokenService.RefreshToken(req.RefreshToken)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    resp := GenerateTokenResponse{
        AccessToken:  token.TokenValue,
        TokenType:    "Bearer",
        ExpiresIn:    int64(time.Until(token.ExpiresAt).Seconds()),
        RefreshToken: token.RefreshToken,
        Scope:        token.Scope,
    }
    
    c.JSON(http.StatusOK, resp)
}

// VerifyToken 验证Token
func (h *TokenHandler) VerifyToken(c *gin.Context) {
    var req VerifyTokenRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    claims, err := h.tokenService.VerifyToken(req.Token)
    if err != nil {
        c.JSON(http.StatusOK, VerifyTokenResponse{
            Valid:   false,
            Message: err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, VerifyTokenResponse{
        Valid:  true,
        Claims: claims,
    })
}

// RevokeToken 撤销Token
func (h *TokenHandler) RevokeToken(c *gin.Context) {
    tokenID := c.Param("id")
    if tokenID == "" {
        c.JSON(http.StatusBadRequest, gin.H{"error": "token id is required"})
        return
    }
    
    if err := h.tokenService.RevokeToken(tokenID); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusOK, gin.H{"message": "token revoked successfully"})
}

// RevokeUserTokens 撤销用户所有Token
func (h *TokenHandler) RevokeUserTokens(c *gin.Context) {
    userID := c.Param("user_id")
    if userID == "" {
        c.JSON(http.StatusBadRequest, gin.H{"error": "user id is required"})
        return
    }
    
    if err := h.tokenService.RevokeUserTokens(userID); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusOK, gin.H{"message": "user tokens revoked successfully"})
}

5.2 路由注册

// SetupRouter 设置路由
func SetupRouter(tokenService service.TokenService) *gin.Engine {
    r := gin.Default()
    
    // 添加中间件
    r.Use(gin.Recovery())
    r.Use(middleware.CORS())
    r.Use(middleware.Logger())
    
    // 创建Token处理器
    tokenHandler := NewTokenHandler(tokenService)
    
    // Token相关路由
    tokenGroup := r.Group("/api/v1/tokens")
    {
        tokenGroup.POST("/generate", tokenHandler.GenerateToken)
        tokenGroup.POST("/refresh", tokenHandler.RefreshToken)
        tokenGroup.POST("/verify", tokenHandler.VerifyToken)
        tokenGroup.DELETE("/:id", tokenHandler.RevokeToken)
        tokenGroup.DELETE("/user/:user_id", tokenHandler.RevokeUserTokens)
    }
    
    return r
}

六、中间件实现

6.1 Token认证中间件

// AuthMiddleware Token认证中间件
func AuthMiddleware(tokenService service.TokenService) gin.HandlerFunc {
    return func(c *gin.Context) {
        // 从Header中获取Token
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "authorization header is required"})
            c.Abort()
            return
        }
        
        // 解析Bearer Token
        parts := strings.SplitN(authHeader, " ", 2)
        if len(parts) != 2 || parts[0] != "Bearer" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid authorization header format"})
            c.Abort()
            return
        }
        
        tokenString := parts[1]
        
        // 验证Token
        claims, err := tokenService.VerifyToken(tokenString)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token: " + err.Error()})
            c.Abort()
            return
        }
        
        // 将用户信息保存到Context
        c.Set("user_id", claims.UserID)
        c.Set("client_id", claims.ClientID)
        c.Set("scope", claims.Scope)
        
        c.Next()
    }
}

6.2 权限验证中间件

// ScopeRequired 权限验证中间件
func ScopeRequired(requiredScope string) gin.HandlerFunc {
    return func(c *gin.Context) {
        // 获取用户权限
        scope, exists := c.Get("scope")
        if !exists {
            c.JSON(http.StatusForbidden, gin.H{"error": "scope not found in context"})
            c.Abort()
            return
        }
        
        scopeStr, ok := scope.(string)
        if !ok {
            c.JSON(http.StatusForbidden, gin.H{"error": "invalid scope type"})
            c.Abort()
            return
        }
        
        // 检查权限
        scopes := strings.Split(scopeStr, " ")
        hasScope := false
        for _, s := range scopes {
            if s == requiredScope {
                hasScope = true
                break
            }
        }
        
        if !hasScope {
            c.JSON(http.StatusForbidden, gin.H{"error": "insufficient scope"})
            c.Abort()
            return
        }
        
        c.Next()
    }
}

七、配置管理

7.1 配置文件

# config.yaml
server:
  port: 8080
  mode: "release"

database:
  host: "localhost"
  port: 3306
  user: "root"
  password: "password"
  name: "token_manager"
  max_idle_conns: 10
  max_open_conns: 100
  conn_max_lifetime: "1h"

redis:
  addr: "localhost:6379"
  password: ""
  db: 0
  pool_size: 100
  min_idle_conns: 10

jwt:
  secret: "your-secret-key"
  issuer: "token-manager"
  access_token_expiry: "2h"
  refresh_token_expiry: "720h"  # 30天

log:
  level: "info"
  output: "stdout"

7.2 配置结构体

// Config 配置结构体
type Config struct {
    Server   ServerConfig   `mapstructure:"server"`
    Database DatabaseConfig `mapstructure:"database"`
    Redis    RedisConfig    `mapstructure:"redis"`
    JWT      JWTConfig      `mapstructure:"jwt"`
    Log      LogConfig      `mapstructure:"log"`
}

// ServerConfig 服务器配置
type ServerConfig struct {
    Port int    `mapstructure:"port"`
    Mode string `mapstructure:"mode"`
}

// DatabaseConfig 数据库配置
type DatabaseConfig struct {
    Host            string `mapstructure:"host"`
    Port            int    `mapstructure:"port"`
    User            string `mapstructure:"user"`
    Password        string `mapstructure:"password"`
    Name            string `mapstructure:"name"`
    MaxIdleConns    int    `mapstructure:"max_idle_conns"`
    MaxOpenConns    int    `mapstructure:"max_open_conns"`
    ConnMaxLifetime string `mapstructure:"conn_max_lifetime"`
}

// RedisConfig Redis配置
type RedisConfig struct {
    Addr         string `mapstructure:"addr"`
    Password     string `mapstructure:"password"`
    DB           int    `mapstructure:"db"`
    PoolSize     int    `mapstructure:"pool_size"`
    MinIdleConns int    `mapstructure:"min_idle_conns"`
}

// JWTConfig JWT配置
type JWTConfig struct {
    Secret             string `mapstructure:"secret"`
    Issuer             string `mapstructure:"issuer"`
    AccessTokenExpiry  string `mapstructure:"access_token_expiry"`
    RefreshTokenExpiry string `mapstructure:"refresh_token_expiry"`
}

// LogConfig 日志配置
type LogConfig struct {
    Level  string `mapstructure:"level"`
    Output string `mapstructure:"output"`
}

// LoadConfig 加载配置
func LoadConfig(path string) (*Config, error) {
    viper.SetConfigFile(path)
    viper.SetConfigType("yaml")
    
    if err := viper.ReadInConfig(); err != nil {
        return nil, fmt.Errorf("failed to read config file: %v", err)
    }
    
    var config Config
    if err := viper.Unmarshal(&config); err != nil {
        return nil, fmt.Errorf("failed to unmarshal config: %v", err)
    }
    
    return &config, nil
}

八、应用启动

8.1 主函数

// main.go
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
    
    "github.com/gin-gonic/gin"
    "github.com/redis/go-redis/v9"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    
    "token-manager/config"
    "token-manager/repository"
    "token-manager/service"
)

func main() {
    // 加载配置
    cfg, err := config.LoadConfig("config.yaml")
    if err != nil {
        log.Fatalf("failed to load config: %v", err)
    }
    
    // 初始化数据库
    db, err := initDatabase(cfg.Database)
    if err != nil {
        log.Fatalf("failed to init database: %v", err)
    }
    
    // 初始化Redis
    redisClient := initRedis(cfg.Redis)
    
    // 创建Token存储
    tokenRepo := repository.NewMySQLTokenRepository(db)
    
    // 创建Token服务
    tokenService, err := service.NewTokenService(cfg, tokenRepo, redisClient)
    if err != nil {
        log.Fatalf("failed to create token service: %v", err)
    }
    
    // 设置Gin模式
    if cfg.Server.Mode == "release" {
        gin.SetMode(gin.ReleaseMode)
    }
    
    // 创建路由
    r := SetupRouter(tokenService)
    
    // 启动服务器
    srv := &http.Server{
        Addr:    fmt.Sprintf(":%d", cfg.Server.Port),
        Handler: r,
    }
    
    // 优雅关闭
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()
    
    log.Printf("server started on port %d", cfg.Server.Port)
    
    // 等待中断信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("shutting down server...")
    
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("server forced to shutdown: %v", err)
    }
    
    log.Println("server exited")
}

// initDatabase 初始化数据库
func initDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Name)
    
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }
    
    sqlDB, err := db.DB()
    if err != nil {
        return nil, err
    }
    
    // 设置连接池
    sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
    sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
    
    maxLifetime, err := time.ParseDuration(cfg.ConnMaxLifetime)
    if err != nil {
        return nil, err
    }
    sqlDB.SetConnMaxLifetime(maxLifetime)
    
    // 自动迁移
    if err := db.AutoMigrate(&service.Token{}); err != nil {
        return nil, err
    }
    
    return db, nil
}

// initRedis 初始化Redis
func initRedis(cfg config.RedisConfig) *redis.Client {
    return redis.NewClient(&redis.Options{
        Addr:     cfg.Addr,
        Password: cfg.Password,
        DB:       cfg.DB,
        PoolSize: cfg.PoolSize,
    })
}

九、完整使用示例

9.1 生成Token

# 生成访问令牌
curl -X POST http://localhost:8080/api/v1/tokens/generate \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user123",
    "client_id": "web-app",
    "scope": ["read", "write"],
    "metadata": {"ip": "192.168.1.1", "device": "web"}
  }'

# 响应示例
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 7200,
  "refresh_token": "mF_9.B5f-4.1JqM",
  "scope": "read write"
}

9.2 验证Token

# 验证令牌
curl -X POST http://localhost:8080/api/v1/tokens/verify \
  -H "Content-Type: application/json" \
  -d '{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'

# 响应示例
{
  "valid": true,
  "claims": {
    "user_id": "user123",
    "client_id": "web-app",
    "scope": "read write",
    "metadata": "{\"ip\":\"192.168.1.1\",\"device\":\"web\"}"
  }
}

9.3 刷新Token

# 刷新令牌
curl -X POST http://localhost:8080/api/v1/tokens/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refresh_token": "mF_9.B5f-4.1JqM"
  }'

# 响应示例
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 7200,
  "refresh_token": "new-refresh-token-value",
  "scope": "read write"
}

9.4 撤销Token

# 撤销单个令牌
curl -X DELETE http://localhost:8080/api/v1/tokens/token_id_123

# 撤销用户所有令牌
curl -X DELETE http://localhost:8080/api/v1/tokens/user/user123

9.5 使用中间件保护API

// 在需要保护的API上使用认证中间件
r.GET("/api/v1/protected", AuthMiddleware(tokenService), func(c *gin.Context) {
    userID := c.MustGet("user_id").(string)
    c.JSON(http.StatusOK, gin.H{"message": "Hello " + userID})
})

// 使用权限验证中间件
r.GET("/api/v1/admin", AuthMiddleware(tokenService), ScopeRequired("admin"), func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"message": "Welcome admin"})
})

十、设计说明与最佳实践

10.1 安全性设计

Token安全

  • 使用HS256算法签名JWT,确保Token不被篡改
  • 设置合理的过期时间(访问令牌2小时,刷新令牌30天)
  • 刷新令牌使用不透明Token,不包含敏感信息
  • 支持Token强制撤销,防止Token被盗用

存储安全

  • Token在Redis中设置自动过期,避免内存泄漏
  • 数据库存储Token元数据,支持审计和查询
  • 敏感配置(如JWT密钥)通过环境变量或配置中心管理

传输安全

  • 建议使用HTTPS协议传输Token
  • 在HTTP Header中使用Bearer Token,避免URL参数泄露

10.2 性能优化

缓存策略

  • 使用Redis缓存热点Token,减少数据库查询
  • 设置合理的缓存过期时间,与Token过期时间一致
  • 使用Pipeline批量操作,减少网络往返次数

连接池

  • 数据库连接池大小根据并发量调整
  • Redis连接池大小建议设置为CPU核心数的2-3倍
  • 设置连接最大空闲时间,避免连接泄漏

异步处理

  • Token生成和验证同步处理,保证实时性
  • Token撤销和清理使用异步任务,避免阻塞主流程

10.3 可扩展性

存储扩展

  • 支持多种数据库(MySQL、PostgreSQL、MongoDB)
  • 支持多种缓存(Redis、Memcached)
  • 通过接口抽象,易于扩展新的存储方案

 

本文完结

版权声明:本文为JienDa博主的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
若内容若侵犯到您的权益,请发送邮件至:platform_service@jienda.com我们将第一时间处理!
所有资源仅限于参考和学习,版权归JienDa作者所有,更多请访问JienDa首页。

给TA赞助
共{{data.count}}人
人已赞助
后端

Go语言在高并发高可用系统中的架构设计与工程实践

2025-12-23 10:41:38

后端

CentOS 7 完整部署指南:Nginx + PHP + MySQL 环境搭建

2025-12-24 16:42:00

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索