Golang中如何强制刷新OAuth2的访问令牌

Golang中如何强制刷新OAuth2的访问令牌 强制刷新 OAuth2 中的访问令牌?

我正在使用 oauth2 包来登录并维护 API 中的授权。它通过 HTTP 请求工作得非常好。如果访问令牌过期,客户端会自动使用刷新令牌来刷新访问令牌。如果刷新令牌接近其生命周期末尾,我的逻辑会刷新这两个令牌。完全没有问题,一切运行完美。

最近添加了一个 WebSocket 连接。它使用访问令牌来建立连接。添加的订阅也使用访问令牌。同样完全没有问题。只要是通过 HTTP 请求,就一切正常。

但是,我需要在访问令牌生命周期接近结束时刷新访问令牌,以重新授权 WebSocket 连接。这需要在当前访问令牌过期之前完成。然后,新的访问令牌将用于重新授权 WebSocket 连接。

目前,我尝试在访问令牌过期前大约一分钟执行此操作。我使用的代码片段与在刷新令牌接近过期时刷新两个令牌的代码相同。这个方法似乎不起作用,因为返回的是旧的、接近过期的令牌。也许访问令牌需要过期才能使这个例程工作?

有没有办法强制 tokenSource.Token() 在访问令牌过期之前刷新令牌?或者有没有人有其他方法来实现这个用例?是否可以更改令牌过期时间来操纵 tokenSource 执行刷新?


更多关于Golang中如何强制刷新OAuth2的访问令牌的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

大家好,我找到了之前所提问题的解决方案。针对这个使用场景,有一个内置函数 ReuseTokenSourceWithExpiry 提供了支持。相关文档在此:

https://cs.opensource.google/go/x/oauth2/+/refs/tags/v0.23.0:oauth2.go;l=400

func ReuseTokenSourceWithExpiry(token *Token, src TokenSource, expiryDelta time.Duration) TokenSource

更多关于Golang中如何强制刷新OAuth2的访问令牌的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang中,你可以通过实现自定义的TokenSource来强制刷新OAuth2访问令牌。oauth2包默认只在令牌过期时自动刷新,但你可以通过以下方式实现提前刷新:

package main

import (
    "context"
    "sync"
    "time"
    
    "golang.org/x/oauth2"
)

// ForceRefreshTokenSource 实现强制刷新的TokenSource
type ForceRefreshTokenSource struct {
    src    oauth2.TokenSource
    mu     sync.RWMutex
    token  *oauth2.Token
    config *oauth2.Config
    ctx    context.Context
}

func NewForceRefreshTokenSource(ctx context.Context, config *oauth2.Config, token *oauth2.Token) *ForceRefreshTokenSource {
    return &ForceRefreshTokenSource{
        src:    config.TokenSource(ctx, token),
        token:  token,
        config: config,
        ctx:    ctx,
    }
}

// Token 返回当前令牌,如果接近过期则强制刷新
func (ts *ForceRefreshTokenSource) Token() (*oauth2.Token, error) {
    ts.mu.RLock()
    token := ts.token
    ts.mu.RUnlock()
    
    // 检查令牌是否接近过期(例如:剩余60秒)
    if token != nil && !token.Valid() && token.Expiry.Sub(time.Now()) <= 60*time.Second {
        return ts.forceRefresh()
    }
    
    // 使用原始的TokenSource获取令牌
    newToken, err := ts.src.Token()
    if err != nil {
        return nil, err
    }
    
    ts.mu.Lock()
    ts.token = newToken
    ts.mu.Unlock()
    
    return newToken, nil
}

// ForceRefresh 强制刷新令牌
func (ts *ForceRefreshTokenSource) ForceRefresh() (*oauth2.Token, error) {
    return ts.forceRefresh()
}

func (ts *ForceRefreshTokenSource) forceRefresh() (*oauth2.Token, error) {
    ts.mu.Lock()
    defer ts.mu.Unlock()
    
    // 如果有刷新令牌,使用它获取新令牌
    if ts.token != nil && ts.token.RefreshToken != "" {
        newToken, err := ts.config.TokenSource(ts.ctx, ts.token).Token()
        if err != nil {
            return nil, err
        }
        ts.token = newToken
        return newToken, nil
    }
    
    // 如果没有刷新令牌,重新进行授权流程
    // 这里需要根据你的具体实现来获取新令牌
    return ts.token, nil
}

// 使用示例
func main() {
    ctx := context.Background()
    
    config := &oauth2.Config{
        ClientID:     "your-client-id",
        ClientSecret: "your-client-secret",
        Scopes:       []string{"scope1", "scope2"},
        Endpoint: oauth2.Endpoint{
            AuthURL:  "https://provider.com/oauth2/auth",
            TokenURL: "https://provider.com/oauth2/token",
        },
    }
    
    // 假设你已经有了初始令牌
    initialToken := &oauth2.Token{
        AccessToken:  "initial-access-token",
        RefreshToken: "initial-refresh-token",
        Expiry:       time.Now().Add(1 * time.Hour),
    }
    
    // 创建支持强制刷新的TokenSource
    tokenSource := NewForceRefreshTokenSource(ctx, config, initialToken)
    
    // 正常获取令牌(会自动检查是否接近过期)
    token, err := tokenSource.Token()
    if err != nil {
        // 处理错误
    }
    
    // 强制刷新令牌(无论是否接近过期)
    freshToken, err := tokenSource.ForceRefresh()
    if err != nil {
        // 处理错误
    }
    
    // 使用新令牌重新授权WebSocket连接
    _ = token
    _ = freshToken
}

对于WebSocket连接的重新授权,你可以这样实现:

// WebSocket管理器示例
type WebSocketManager struct {
    conn        *websocket.Conn
    tokenSource *ForceRefreshTokenSource
    mu          sync.RWMutex
}

func (wm *WebSocketManager) ReauthorizeConnection() error {
    wm.mu.Lock()
    defer wm.mu.Unlock()
    
    // 强制刷新令牌
    newToken, err := wm.tokenSource.ForceRefresh()
    if err != nil {
        return err
    }
    
    // 关闭旧连接
    if wm.conn != nil {
        wm.conn.Close()
    }
    
    // 使用新令牌建立WebSocket连接
    headers := http.Header{}
    headers.Set("Authorization", "Bearer "+newToken.AccessToken)
    
    conn, _, err := websocket.DefaultDialer.Dial("wss://your-api.com/ws", headers)
    if err != nil {
        return err
    }
    
    wm.conn = conn
    return nil
}

// 定时检查并刷新令牌
func (wm *WebSocketManager) StartTokenRefreshScheduler(refreshInterval time.Duration) {
    ticker := time.NewTicker(refreshInterval)
    defer ticker.Stop()
    
    for range ticker.C {
        token, err := wm.tokenSource.Token()
        if err != nil {
            // 处理错误
            continue
        }
        
        // 如果令牌接近过期,重新授权WebSocket连接
        if token.Expiry.Sub(time.Now()) <= 60*time.Second {
            if err := wm.ReauthorizeConnection(); err != nil {
                // 处理重新授权失败
            }
        }
    }
}

这种方法允许你在访问令牌过期前强制刷新,确保WebSocket连接在令牌失效前重新建立授权。

回到顶部