feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
package auth
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
2026-04-03 17:38:31 +08:00
|
|
|
|
"sync"
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-03 17:38:31 +08:00
|
|
|
|
const (
|
|
|
|
|
|
// MaxSessions 最大 session 数量限制
|
|
|
|
|
|
MaxSessions = 10000
|
|
|
|
|
|
// CleanupInterval 清理间隔
|
|
|
|
|
|
CleanupInterval = 5 * time.Minute
|
|
|
|
|
|
)
|
|
|
|
|
|
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
// SSOOAuth2Config SSO OAuth2 配置
|
|
|
|
|
|
type SSOOAuth2Config struct {
|
|
|
|
|
|
ClientID string
|
|
|
|
|
|
ClientSecret string
|
|
|
|
|
|
RedirectURI string
|
|
|
|
|
|
Scope string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSOProvider SSO 提供者接口
|
|
|
|
|
|
type SSOProvider interface {
|
|
|
|
|
|
// Authorize 处理授权请求
|
|
|
|
|
|
Authorize(ctx context.Context, req *SSOAuthorizeRequest) (*SSOAuthorizeResponse, error)
|
|
|
|
|
|
// Introspect 验证 access token
|
|
|
|
|
|
Introspect(ctx context.Context, token string) (*SSOTokenInfo, error)
|
|
|
|
|
|
// Revoke 撤销 token
|
|
|
|
|
|
Revoke(ctx context.Context, token string) error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSOAuthorizeRequest 授权请求
|
|
|
|
|
|
type SSOAuthorizeRequest struct {
|
|
|
|
|
|
ClientID string
|
|
|
|
|
|
RedirectURI string
|
|
|
|
|
|
ResponseType string // "code" 或 "token"
|
|
|
|
|
|
Scope string
|
|
|
|
|
|
State string
|
|
|
|
|
|
UserID int64
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSOAuthorizeResponse 授权响应
|
|
|
|
|
|
type SSOAuthorizeResponse struct {
|
|
|
|
|
|
Code string // 授权码(authorization_code 模式)
|
|
|
|
|
|
State string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSOTokenInfo Token 信息
|
|
|
|
|
|
type SSOTokenInfo struct {
|
|
|
|
|
|
Active bool
|
|
|
|
|
|
UserID int64
|
|
|
|
|
|
Username string
|
|
|
|
|
|
ExpiresAt time.Time
|
|
|
|
|
|
Scope string
|
|
|
|
|
|
ClientID string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSOSession SSO Session
|
|
|
|
|
|
type SSOSession struct {
|
|
|
|
|
|
SessionID string
|
|
|
|
|
|
UserID int64
|
|
|
|
|
|
Username string
|
|
|
|
|
|
ClientID string
|
|
|
|
|
|
CreatedAt time.Time
|
|
|
|
|
|
ExpiresAt time.Time
|
|
|
|
|
|
Scope string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSOManager SSO 管理器
|
|
|
|
|
|
type SSOManager struct {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
mu sync.RWMutex
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
sessions map[string]*SSOSession
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewSSOManager 创建 SSO 管理器
|
|
|
|
|
|
func NewSSOManager() *SSOManager {
|
|
|
|
|
|
return &SSOManager{
|
|
|
|
|
|
sessions: make(map[string]*SSOSession),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 17:38:31 +08:00
|
|
|
|
// StartCleanup 启动后台清理 goroutine
|
|
|
|
|
|
func (m *SSOManager) StartCleanup(ctx context.Context) {
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
ticker := time.NewTicker(CleanupInterval)
|
|
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
for {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
return
|
|
|
|
|
|
case <-ticker.C:
|
|
|
|
|
|
m.CleanupExpired()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
// GenerateAuthorizationCode 生成授权码
|
|
|
|
|
|
func (m *SSOManager) GenerateAuthorizationCode(clientID, redirectURI, scope string, userID int64, username string) (string, error) {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
code, err := generateSecureToken(32)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
sessionID, err := generateSecureToken(16)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
|
|
|
|
|
|
session := &SSOSession{
|
2026-04-03 17:38:31 +08:00
|
|
|
|
SessionID: sessionID,
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
UserID: userID,
|
|
|
|
|
|
Username: username,
|
|
|
|
|
|
ClientID: clientID,
|
|
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
|
|
ExpiresAt: time.Now().Add(10 * time.Minute), // 授权码 10 分钟有效期
|
|
|
|
|
|
Scope: scope,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.Lock()
|
|
|
|
|
|
// 检查并清理过期 session,如果超过限制则淘汰最旧的
|
|
|
|
|
|
if len(m.sessions) >= MaxSessions {
|
|
|
|
|
|
m.cleanupExpiredLocked()
|
|
|
|
|
|
// 如果仍然满,淘汰最早的
|
|
|
|
|
|
if len(m.sessions) >= MaxSessions {
|
|
|
|
|
|
m.evictOldest()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
m.sessions[code] = session
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.Unlock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
|
|
|
|
|
|
return code, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ValidateAuthorizationCode 验证授权码
|
|
|
|
|
|
func (m *SSOManager) ValidateAuthorizationCode(code string) (*SSOSession, error) {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.Lock()
|
|
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
|
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
session, ok := m.sessions[code]
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
return nil, errors.New("invalid authorization code")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if time.Now().After(session.ExpiresAt) {
|
|
|
|
|
|
delete(m.sessions, code)
|
|
|
|
|
|
return nil, errors.New("authorization code expired")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用后删除
|
|
|
|
|
|
delete(m.sessions, code)
|
|
|
|
|
|
|
|
|
|
|
|
return session, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GenerateAccessToken 生成访问令牌
|
2026-04-03 17:38:31 +08:00
|
|
|
|
func (m *SSOManager) GenerateAccessToken(clientID string, session *SSOSession) (string, time.Time, error) {
|
|
|
|
|
|
token, err := generateSecureToken(32)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", time.Time{}, err
|
|
|
|
|
|
}
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
expiresAt := time.Now().Add(2 * time.Hour) // Access token 2 小时有效期
|
|
|
|
|
|
|
|
|
|
|
|
accessSession := &SSOSession{
|
|
|
|
|
|
SessionID: token,
|
|
|
|
|
|
UserID: session.UserID,
|
|
|
|
|
|
Username: session.Username,
|
|
|
|
|
|
ClientID: clientID,
|
|
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
|
|
ExpiresAt: expiresAt,
|
|
|
|
|
|
Scope: session.Scope,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.Lock()
|
|
|
|
|
|
// 检查并清理过期 session,如果超过限制则淘汰最旧的
|
|
|
|
|
|
if len(m.sessions) >= MaxSessions {
|
|
|
|
|
|
m.cleanupExpiredLocked()
|
|
|
|
|
|
if len(m.sessions) >= MaxSessions {
|
|
|
|
|
|
m.evictOldest()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
m.sessions[token] = accessSession
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.Unlock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
|
2026-04-03 17:38:31 +08:00
|
|
|
|
return token, expiresAt, nil
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IntrospectToken 验证 token
|
|
|
|
|
|
func (m *SSOManager) IntrospectToken(token string) (*SSOTokenInfo, error) {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.RLock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
session, ok := m.sessions[token]
|
|
|
|
|
|
if !ok {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.RUnlock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
return &SSOTokenInfo{Active: false}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if time.Now().After(session.ExpiresAt) {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.RUnlock()
|
|
|
|
|
|
m.mu.Lock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
delete(m.sessions, token)
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.Unlock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
return &SSOTokenInfo{Active: false}, nil
|
|
|
|
|
|
}
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.RUnlock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
|
|
|
|
|
|
return &SSOTokenInfo{
|
|
|
|
|
|
Active: true,
|
|
|
|
|
|
UserID: session.UserID,
|
|
|
|
|
|
Username: session.Username,
|
|
|
|
|
|
ExpiresAt: session.ExpiresAt,
|
|
|
|
|
|
Scope: session.Scope,
|
|
|
|
|
|
ClientID: session.ClientID,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RevokeToken 撤销 token
|
|
|
|
|
|
func (m *SSOManager) RevokeToken(token string) error {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.Lock()
|
|
|
|
|
|
defer m.mu.Unlock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
delete(m.sessions, token)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 17:38:31 +08:00
|
|
|
|
// CleanupExpired 清理过期的 session
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
func (m *SSOManager) CleanupExpired() {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
m.mu.Lock()
|
|
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
|
m.cleanupExpiredLocked()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// cleanupExpiredLocked 内部清理方法(假设已持有锁)
|
|
|
|
|
|
func (m *SSOManager) cleanupExpiredLocked() {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
now := time.Now()
|
|
|
|
|
|
for key, session := range m.sessions {
|
|
|
|
|
|
if now.After(session.ExpiresAt) {
|
|
|
|
|
|
delete(m.sessions, key)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 17:38:31 +08:00
|
|
|
|
// evictOldest 淘汰最早的 session(假设已持有锁)
|
|
|
|
|
|
func (m *SSOManager) evictOldest() {
|
|
|
|
|
|
if len(m.sessions) == 0 {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
var oldestKey string
|
|
|
|
|
|
var oldestTime time.Time
|
|
|
|
|
|
for key, session := range m.sessions {
|
|
|
|
|
|
if oldestTime.IsZero() || session.CreatedAt.Before(oldestTime) {
|
|
|
|
|
|
oldestTime = session.CreatedAt
|
|
|
|
|
|
oldestKey = key
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if oldestKey != "" {
|
|
|
|
|
|
delete(m.sessions, oldestKey)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SessionCount 返回当前 session 数量(用于监控)
|
|
|
|
|
|
func (m *SSOManager) SessionCount() int {
|
|
|
|
|
|
m.mu.RLock()
|
|
|
|
|
|
defer m.mu.RUnlock()
|
|
|
|
|
|
return len(m.sessions)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
// generateSecureToken 生成安全随机 token
|
2026-04-03 17:38:31 +08:00
|
|
|
|
func generateSecureToken(length int) (string, error) {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
bytes := make([]byte, length)
|
2026-04-03 17:38:31 +08:00
|
|
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("failed to generate secure token: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return base64.URLEncoding.EncodeToString(bytes)[:length], nil
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSOClient SSO 客户端配置存储
|
|
|
|
|
|
type SSOClient struct {
|
|
|
|
|
|
ClientID string
|
|
|
|
|
|
ClientSecret string
|
|
|
|
|
|
Name string
|
|
|
|
|
|
RedirectURIs []string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSOClientsStore SSO 客户端存储接口
|
|
|
|
|
|
type SSOClientsStore interface {
|
|
|
|
|
|
GetByClientID(clientID string) (*SSOClient, error)
|
2026-04-03 17:38:31 +08:00
|
|
|
|
ValidateClientRedirectURI(clientID, redirectURI string) bool
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DefaultSSOClientsStore 默认内存存储
|
|
|
|
|
|
type DefaultSSOClientsStore struct {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
mu sync.RWMutex
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
clients map[string]*SSOClient
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewDefaultSSOClientsStore 创建默认客户端存储
|
|
|
|
|
|
func NewDefaultSSOClientsStore() *DefaultSSOClientsStore {
|
|
|
|
|
|
return &DefaultSSOClientsStore{
|
|
|
|
|
|
clients: make(map[string]*SSOClient),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RegisterClient 注册客户端
|
|
|
|
|
|
func (s *DefaultSSOClientsStore) RegisterClient(client *SSOClient) {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
s.mu.Lock()
|
|
|
|
|
|
defer s.mu.Unlock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
s.clients[client.ClientID] = client
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetByClientID 根据 ClientID 获取客户端
|
|
|
|
|
|
func (s *DefaultSSOClientsStore) GetByClientID(clientID string) (*SSOClient, error) {
|
2026-04-03 17:38:31 +08:00
|
|
|
|
s.mu.RLock()
|
|
|
|
|
|
defer s.mu.RUnlock()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
client, ok := s.clients[clientID]
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
return nil, fmt.Errorf("client not found: %s", clientID)
|
|
|
|
|
|
}
|
|
|
|
|
|
return client, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ValidateClientRedirectURI 验证客户端的 RedirectURI
|
|
|
|
|
|
func (s *DefaultSSOClientsStore) ValidateClientRedirectURI(clientID, redirectURI string) bool {
|
|
|
|
|
|
client, err := s.GetByClientID(clientID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, uri := range client.RedirectURIs {
|
|
|
|
|
|
if uri == redirectURI {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|