mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
63cd906cfc
- Merge all files inside openflare-server to the repository root directory. - Relocate agent, relay, and flared subprojects from internal/ to internal/apps/. - Combine docker-compose files and update build context paths to root. - Update GitHub workflows and Dockerfiles to refer to new directories and package names. - Rewrite Go package imports across all files. - Resolve database renew test race condition and clean up docs.
100 lines
3.2 KiB
Go
100 lines
3.2 KiB
Go
// Copyright 2026 Arctel.net
|
||
// SPDX-License-Identifier: Apache-2.0
|
||
|
||
package oauth
|
||
|
||
import (
|
||
"context"
|
||
"net/http"
|
||
"sync"
|
||
|
||
"github.com/coreos/go-oidc/v3/oidc"
|
||
"golang.org/x/oauth2"
|
||
"golang.org/x/sync/singleflight"
|
||
)
|
||
|
||
// oidcProviderCache 进程级 OIDC provider 缓存。
|
||
//
|
||
// oidc.NewProvider 每次调用都会向远端 issuer 的
|
||
// /.well-known/openid-configuration 发起 HTTP 请求拉取元数据。
|
||
// 由于 provider 元数据极少变动,将其缓存后可消除登录发起与回调时的
|
||
// 重复外部 HTTP 往返。
|
||
//
|
||
// 并发安全性:
|
||
// - mu + entries 防止并发读写 map。
|
||
// - sfGroup 保证同一 issuer 同时只有一次在途的 NewProvider 调用
|
||
// (singleflight),后续等待者复用同一结果,彻底消除 thundering herd。
|
||
type oidcProviderCache struct {
|
||
mu sync.RWMutex
|
||
entries map[string]*oidc.Provider // key: normalized issuer URL
|
||
sfGroup singleflight.Group
|
||
}
|
||
|
||
// globalOIDCProviderCache 是包级单例缓存,与进程同生命周期。
|
||
var globalOIDCProviderCache = &oidcProviderCache{
|
||
entries: make(map[string]*oidc.Provider),
|
||
}
|
||
|
||
// discoveryContext 从请求 ctx 提取 HTTP 客户端,并绑定到不可取消的 Background ctx。
|
||
// 这样既能在测试中注入 mock 客户端,又避免请求取消导致 provider 拉取失败。
|
||
func discoveryContext(ctx context.Context) context.Context {
|
||
bg := context.Background()
|
||
if client, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok && client != nil {
|
||
bg = oidc.ClientContext(bg, client)
|
||
}
|
||
return bg
|
||
}
|
||
|
||
// get 返回缓存的 provider;若无则通过 oidc.NewProvider 获取并写入缓存。
|
||
// 同一 issuer 并发调用时,singleflight 保证只有一次实际 HTTP 请求。
|
||
func (c *oidcProviderCache) get(ctx context.Context, issuer string) (*oidc.Provider, error) {
|
||
// 快路径:已有缓存则直接返回。
|
||
c.mu.RLock()
|
||
if p, ok := c.entries[issuer]; ok {
|
||
c.mu.RUnlock()
|
||
return p, nil
|
||
}
|
||
c.mu.RUnlock()
|
||
|
||
// 慢路径:通过 singleflight 合并并发的首次请求。
|
||
discCtx := discoveryContext(ctx)
|
||
v, err, _ := c.sfGroup.Do(issuer, func() (any, error) {
|
||
// 双检:singleflight 内再次检查,前一个并发组可能已写入缓存。
|
||
c.mu.RLock()
|
||
if p, ok := c.entries[issuer]; ok {
|
||
c.mu.RUnlock()
|
||
return p, nil
|
||
}
|
||
c.mu.RUnlock()
|
||
|
||
p, err := oidc.NewProvider(discCtx, issuer)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
c.mu.Lock()
|
||
c.entries[issuer] = p
|
||
c.mu.Unlock()
|
||
return p, nil
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return v.(*oidc.Provider), nil //nolint:forcetypeassert // singleflight value 由同函数写入,类型确定
|
||
}
|
||
|
||
// invalidate 从缓存中移除指定 issuer 对应的 provider。
|
||
// 在认证源的 Discovery URL 被修改时调用,强制下次请求重新拉取元数据。
|
||
func (c *oidcProviderCache) invalidate(issuer string) {
|
||
c.mu.Lock()
|
||
delete(c.entries, issuer)
|
||
c.mu.Unlock()
|
||
}
|
||
|
||
// InvalidateOIDCProviderCache 从进程级缓存中清除指定 issuer 的 provider 条目。
|
||
// 当管理员更新认证源的 Discovery URL 后调用,以确保下次登录时重新拉取最新元数据。
|
||
// issuer 值应为去掉 /.well-known/openid-configuration 后缀的规范化 URL。
|
||
func InvalidateOIDCProviderCache(issuer string) {
|
||
globalOIDCProviderCache.invalidate(issuer)
|
||
}
|