forked from baron/baron-sso
외부 OIDC IdP 연동 계획
This commit is contained in:
91
backend/internal/service/federation_service.go
Normal file
91
backend/internal/service/federation_service.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/repository"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
)
|
||||
|
||||
type FederationService struct {
|
||||
repo repository.FederationRepository
|
||||
hydraSvc *HydraAdminService
|
||||
redisSvc *RedisService
|
||||
}
|
||||
|
||||
func NewFederationService(repo repository.FederationRepository, hydraSvc *HydraAdminService, redisSvc *RedisService) *FederationService {
|
||||
return &FederationService{repo: repo, hydraSvc: hydraSvc, redisSvc: redisSvc}
|
||||
}
|
||||
|
||||
func (s *FederationService) InitiateOIDCLogin(ctx context.Context, providerID, loginChallenge string) (string, error) {
|
||||
provider, err := s.repo.FindProviderByID(ctx, providerID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to find provider: %w", err)
|
||||
}
|
||||
|
||||
if provider == nil || provider.IssuerURL == nil || provider.ClientID == nil || provider.ClientSecret == nil || provider.Scopes == nil {
|
||||
return "", fmt.Errorf("OIDC configuration for provider %s is incomplete", providerID)
|
||||
}
|
||||
|
||||
oidcProvider, err := oidc.NewProvider(ctx, *provider.IssuerURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create OIDC provider: %w", err)
|
||||
}
|
||||
|
||||
config := oauth2.Config{
|
||||
ClientID: *provider.ClientID,
|
||||
ClientSecret: *provider.ClientSecret,
|
||||
Endpoint: oidcProvider.Endpoint(),
|
||||
RedirectURL: "http://localhost:8080/api/v1/federation/oidc/callback", // This should be configurable
|
||||
Scopes: []string{*provider.Scopes},
|
||||
}
|
||||
|
||||
state, err := generateState()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate state: %w", err)
|
||||
}
|
||||
|
||||
// Store state and login_challenge in Redis
|
||||
redisKey := fmt.Sprintf("oidc_state:%s", state)
|
||||
if err := s.redisSvc.Set(redisKey, loginChallenge, 10*time.Minute); err != nil {
|
||||
return "", fmt.Errorf("failed to save state to Redis: %w", err)
|
||||
}
|
||||
|
||||
return config.AuthCodeURL(state), nil
|
||||
}
|
||||
|
||||
func (s *FederationService) HandleOIDCCallback(ctx context.Context, code, state string) (string, error) {
|
||||
// 1. Retrieve login_challenge from Redis
|
||||
redisKey := fmt.Sprintf("oidc_state:%s", state)
|
||||
loginChallenge, err := s.redisSvc.Get(redisKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get state from Redis or state expired: %w", err)
|
||||
}
|
||||
// Delete the state from Redis now that it's been used
|
||||
s.redisSvc.Delete(redisKey)
|
||||
|
||||
// TODO: Finish the rest of the callback logic
|
||||
// 2. Exchange code for token
|
||||
// 3. Verify ID token
|
||||
// 4. JIT Provisioning
|
||||
// 5. Accept Hydra Login Request
|
||||
|
||||
fmt.Println("Login challenge found:", loginChallenge)
|
||||
|
||||
return "http://localhost:3000/login?login_successful=true", nil // Placeholder
|
||||
}
|
||||
|
||||
|
||||
func generateState() (string, error) {
|
||||
b := make([]byte, 32)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
Reference in New Issue
Block a user