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
}
这个实现提供了:
- 自动令牌刷新机制
- 线程安全的并发访问
- 提前刷新避免请求中断
- 可扩展的缓存层
- 与标准
http.Client兼容的接口

