Golang中如何实现HTTP客户端自动刷新过期的访问令牌?

Golang中如何实现HTTP客户端自动刷新过期的访问令牌? 我需要使用一个HTTP客户端来进行一些调用(大约每秒10到100次)。我使用的Bearer令牌会在30分钟后过期。

有没有推荐的HTTP客户端可以处理这种情况?目前我考虑使用默认的HTTP客户端(带超时设置),并搭配我找到的某种TTL缓存实现。

有什么建议吗?

2 回复

你是否在使用 OAuth?你可以研究一下 https://github.com/golang/oauth2,它会为你刷新令牌。

更多关于Golang中如何实现HTTP客户端自动刷新过期的访问令牌?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang中实现HTTP客户端自动刷新过期访问令牌,推荐使用http.RoundTripper接口包装默认客户端。以下是一个完整的实现示例:

package main

import (
    "context"
    "fmt"
    "net/http"
    "sync"
    "time"
)

type TokenRefresher interface {
    RefreshToken() (string, error)
}

type AutoRefreshClient struct {
    client        *http.Client
    tokenRefresher TokenRefresher
    token         string
    tokenExpiry   time.Time
    mu            sync.RWMutex
    refreshMargin time.Duration
}

func NewAutoRefreshClient(refresher TokenRefresher) *AutoRefreshClient {
    return &AutoRefreshClient{
        client: &http.Client{
            Timeout: 30 * time.Second,
        },
        tokenRefresher: refresher,
        refreshMargin:  5 * time.Minute, // 提前5分钟刷新
    }
}

func (c *AutoRefreshClient) RoundTrip(req *http.Request) (*http.Response, error) {
    // 获取或刷新令牌
    token, err := c.getValidToken()
    if err != nil {
        return nil, err
    }
    
    // 设置Authorization头
    req.Header.Set("Authorization", "Bearer "+token)
    
    // 执行请求
    return c.client.Transport.RoundTrip(req)
}

func (c *AutoRefreshClient) getValidToken() (string, error) {
    c.mu.RLock()
    token := c.token
    expiry := c.tokenExpiry
    c.mu.RUnlock()

    // 检查令牌是否即将过期
    if token == "" || time.Until(expiry) <= c.refreshMargin {
        return c.refreshToken()
    }
    
    return token, nil
}

func (c *AutoRefreshClient) refreshToken() (string, error) {
    c.mu.Lock()
    defer c.mu.Unlock()
    
    // 双重检查,防止多个goroutine同时刷新
    if c.token != "" && time.Until(c.tokenExpiry) > c.refreshMargin {
        return c.token, nil
    }
    
    newToken, err := c.tokenRefresher.RefreshToken()
    if err != nil {
        return "", fmt.Errorf("刷新令牌失败: %w", err)
    }
    
    c.token = newToken
    c.tokenExpiry = time.Now().Add(30 * time.Minute) // 30分钟有效期
    
    return newToken, nil
}

func (c *AutoRefreshClient) Do(req *http.Request) (*http.Response, error) {
    // 使用自定义的RoundTripper
    if c.client.Transport == nil {
        c.client.Transport = http.DefaultTransport
    }
    
    // 包装RoundTripper
    originalTransport := c.client.Transport
    c.client.Transport = &roundTripperWrapper{
        base: originalTransport,
        client: c,
    }
    
    defer func() {
        c.client.Transport = originalTransport
    }()
    
    return c.client.Do(req)
}

type roundTripperWrapper struct {
    base   http.RoundTripper
    client *AutoRefreshClient
}

func (w *roundTripperWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
    return w.client.RoundTrip(req)
}

// 示例TokenRefresher实现
type ExampleTokenRefresher struct {
    apiURL string
}

func (r *ExampleTokenRefresher) RefreshToken() (string, error) {
    // 实现实际的令牌刷新逻辑
    resp, err := http.Post(r.apiURL, "application/json", nil)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    
    // 解析响应获取新令牌
    // 这里返回模拟令牌
    return "new-access-token-" + time.Now().Format("150405"), nil
}

// 使用示例
func main() {
    refresher := &ExampleTokenRefresher{
        apiURL: "https://api.example.com/token",
    }
    
    client := NewAutoRefreshClient(refresher)
    
    // 创建请求
    req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
    
    // 执行请求(会自动处理令牌刷新)
    resp, err := client.Do(req)
    if err != nil {
        fmt.Printf("请求失败: %v\n", err)
        return
    }
    defer resp.Body.Close()
    
    fmt.Println("请求成功")
}

对于高并发场景(每秒10-100次请求),可以添加令牌缓存和并发控制:

type CachedTokenClient struct {
    *AutoRefreshClient
    cache *TokenCache
}

type TokenCache struct {
    tokens map[string]*cacheEntry
    mu     sync.RWMutex
}

type cacheEntry struct {
    token   string
    expiry  time.Time
    refreshing bool
    cond    *sync.Cond
}

func NewCachedTokenClient(refresher TokenRefresher) *CachedTokenClient {
    return &CachedTokenClient{
        AutoRefreshClient: NewAutoRefreshClient(refresher),
        cache: &TokenCache{
            tokens: make(map[string]*cacheEntry),
        },
    }
}

func (c *CachedTokenClient) GetToken(key string) (string, error) {
    c.cache.mu.RLock()
    entry, exists := c.cache.tokens[key]
    c.cache.mu.RUnlock()
    
    if exists && time.Until(entry.expiry) > 5*time.Minute {
        return entry.token, nil
    }
    
    // 获取写锁进行刷新
    c.cache.mu.Lock()
    defer c.cache.mu.Unlock()
    
    // 再次检查
    entry, exists = c.cache.tokens[key]
    if exists && time.Until(entry.expiry) > 5*time.Minute {
        return entry.token, nil
    }
    
    // 刷新令牌
    newToken, err := c.tokenRefresher.RefreshToken()
    if err != nil {
        return "", err
    }
    
    c.cache.tokens[key] = &cacheEntry{
        token:  newToken,
        expiry: time.Now().Add(30 * time.Minute),
    }
    
    return newToken, nil
}

这个实现提供了:

  1. 自动令牌刷新机制
  2. 线程安全的并发访问
  3. 提前刷新避免请求中断
  4. 可扩展的缓存层
  5. 与标准http.Client兼容的接口
回到顶部