Skip to content

Commit 3627064

Browse files
committed
feat: 서비스 제공자 확장에 따른 리패팩토링
1 parent ac312b4 commit 3627064

File tree

9 files changed

+416
-193
lines changed

9 files changed

+416
-193
lines changed

ProjectVG.Api/Controllers/OAuthController.cs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using ProjectVG.Application.Services.Auth;
33
using Microsoft.Extensions.Options;
44
using ProjectVG.Common.Configuration;
5+
using ProjectVG.Common.Exceptions;
6+
using ProjectVG.Common.Constants;
57

68
namespace ProjectVG.Api.Controllers
79
{
@@ -10,46 +12,73 @@ namespace ProjectVG.Api.Controllers
1012
public class OAuthController : ControllerBase
1113
{
1214
private readonly IOAuth2Service _oauth2Service;
15+
private readonly IOAuth2ProviderFactory _providerFactory;
1316

1417
public OAuthController(
1518
IOAuth2Service oauth2Service,
19+
IOAuth2ProviderFactory providerFactory,
1620
IOptions<OAuth2ProviderSettings> oauth2Settings)
1721
{
1822
_oauth2Service = oauth2Service;
23+
_providerFactory = providerFactory;
1924
}
2025

21-
[HttpGet("oauth2/authorize")]
26+
[HttpGet("oauth2/providers")]
27+
public IActionResult GetSupportedProviders()
28+
{
29+
var providers = _providerFactory.GetSupportedProviders();
30+
return Ok(new
31+
{
32+
success = true,
33+
providers = providers.ToList()
34+
});
35+
}
36+
37+
[HttpGet("oauth2/authorize/{provider}")]
2238
public async Task<IActionResult> OAuth2Authorize(
39+
string provider,
2340
[FromQuery] string state,
2441
[FromQuery] string code_challenge,
2542
[FromQuery] string code_challenge_method,
2643
[FromQuery] string code_verifier,
2744
[FromQuery] string client_redirect_uri)
2845
{
29-
if (string.IsNullOrEmpty(code_challenge) || code_challenge_method != "S256") {
46+
if (!_providerFactory.IsProviderSupported(provider))
47+
{
48+
throw new ValidationException(ErrorCode.OAUTH2_PROVIDER_NOT_SUPPORTED);
49+
}
50+
51+
if (string.IsNullOrEmpty(code_challenge) || code_challenge_method != "S256")
52+
{
3053
throw new ValidationException(ErrorCode.OAUTH2_PKCE_INVALID);
3154
}
3255

33-
var googleAuthUrl = await _oauth2Service.BuildAuthorizationUrlAsync(state, code_challenge, code_challenge_method, code_verifier, client_redirect_uri);
56+
// 제공자별 인증 URL 생성
57+
var authUrl = await _oauth2Service.BuildAuthorizationUrlAsync(provider, state, code_challenge, code_challenge_method, code_verifier, client_redirect_uri);
3458

35-
return Ok(new {
59+
return Ok(new
60+
{
3661
success = true,
37-
auth_url = googleAuthUrl
62+
provider = provider,
63+
auth_url = authUrl
3864
});
3965
}
4066

4167
[HttpGet("oauth2/callback")]
42-
[HttpGet("google/callback")]
68+
[HttpGet("oauth2/callback/{provider}")]
4369
public async Task<IActionResult> OAuth2Callback(
70+
string? provider,
4471
[FromQuery] string code,
4572
[FromQuery] string state,
4673
[FromQuery] string error = null)
4774
{
48-
if (!string.IsNullOrEmpty(error)) {
75+
if (!string.IsNullOrEmpty(error))
76+
{
4977
throw new ValidationException(ErrorCode.OAUTH2_CALLBACK_FAILED);
5078
}
5179

52-
if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(state)) {
80+
if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(state))
81+
{
5382
throw new ValidationException(ErrorCode.REQUIRED_PARAMETER_MISSING);
5483
}
5584

@@ -61,12 +90,14 @@ public async Task<IActionResult> OAuth2Callback(
6190
[HttpGet("oauth2/token")]
6291
public async Task<IActionResult> GetOAuth2Token([FromQuery] string state)
6392
{
64-
if (string.IsNullOrEmpty(state)) {
93+
if (string.IsNullOrEmpty(state))
94+
{
6595
throw new ValidationException(ErrorCode.REQUIRED_PARAMETER_MISSING);
6696
}
6797

6898
var tokenData = await _oauth2Service.GetTokenDataAsync(state);
69-
if (tokenData == null) {
99+
if (tokenData == null)
100+
{
70101
throw new ValidationException(ErrorCode.OAUTH2_REQUEST_NOT_FOUND);
71102
}
72103

@@ -79,6 +110,5 @@ public async Task<IActionResult> GetOAuth2Token([FromQuery] string state)
79110

80111
return Ok(new { success = true });
81112
}
82-
83113
}
84114
}

ProjectVG.Application/ApplicationServiceCollectionExtensions.cs

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,42 @@
11
using Microsoft.Extensions.DependencyInjection;
2+
using ProjectVG.Application.Services.Auth;
23
using ProjectVG.Application.Services.Character;
3-
using ProjectVG.Application.Services.Users;
44
using ProjectVG.Application.Services.Chat;
5-
using ProjectVG.Application.Services.Chat.CostTracking;
6-
using ProjectVG.Application.Services.Chat.Preprocessors;
7-
using ProjectVG.Application.Services.Chat.Processors;
8-
using ProjectVG.Application.Services.Chat.Validators;
9-
using ProjectVG.Application.Services.Chat.Handlers;
10-
using ProjectVG.Application.Services.WebSocket;
115
using ProjectVG.Application.Services.Conversation;
126
using ProjectVG.Application.Services.Session;
13-
using ProjectVG.Application.Services.Auth;
7+
using ProjectVG.Application.Services.Users;
8+
using ProjectVG.Application.Services.WebSocket;
149

1510
namespace ProjectVG.Application
1611
{
1712
public static class ApplicationServiceCollectionExtensions
1813
{
19-
/// <summary>
20-
/// 애플리케이션 서비스 등록
21-
/// </summary>
2214
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
2315
{
24-
services.AddScoped<ICharacterService, CharacterService>();
25-
services.AddScoped<IUserService, UserService>();
16+
// Auth Services
2617
services.AddScoped<IAuthService, AuthService>();
27-
services.AddSingleton<IOAuth2Service, OAuth2Service>();
28-
services.AddScoped<ChatRequestValidator>();
29-
services.AddScoped<ChatLLMProcessor>();
30-
services.AddScoped<ChatTTSProcessor>();
31-
services.AddScoped<ChatResultProcessor>();
32-
services.AddScoped<UserInputAnalysisProcessor>();
33-
services.AddScoped<UserInputActionProcessor>();
34-
services.AddScoped<MemoryContextPreprocessor>();
35-
services.AddScoped<ChatFailureHandler>();
36-
37-
services.AddSingleton<IWebSocketManager, WebSocketManager>();
18+
services.AddScoped<IOAuth2Service, OAuth2Service>();
19+
services.AddScoped<IOAuth2ProviderFactory, OAuth2ProviderFactory>();
20+
21+
// User Services
22+
services.AddScoped<IUserService, UserService>();
23+
24+
// Character Services
25+
services.AddScoped<ICharacterService, CharacterService>();
26+
27+
// Chat Services
3828
services.AddScoped<IChatService, ChatService>();
39-
services.AddScoped<IConversationService, ConversationService>();
40-
services.AddSingleton<IConnectionRegistry, ConnectionRegistry>();
41-
4229
services.AddScoped<IChatMetricsService, ChatMetricsService>();
30+
services.AddScoped<ICostTrackingDecoratorFactory, CostTrackingDecoratorFactory>();
31+
32+
// Conversation Services
33+
services.AddScoped<IConversationService, ConversationService>();
34+
35+
// Session Services
36+
services.AddScoped<IConnectionRegistry, ConnectionRegistry>();
4337

44-
// 비용 추적 데코레이터 등록
45-
services.AddCostTrackingDecorator<ChatLLMProcessor>("LLM_Processing");
46-
services.AddCostTrackingDecorator<ChatTTSProcessor>("TTS_Processing");
47-
services.AddCostTrackingDecorator<UserInputAnalysisProcessor>("User_Input_Analysis");
38+
// WebSocket Services
39+
services.AddScoped<IWebSocketManager, WebSocketManager>();
4840

4941
return services;
5042
}

ProjectVG.Application/Services/Auth/AuthService.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ public async Task<AuthResult> LoginWithOAuthAsync(string provider, string provid
5555
_logger.LogInformation("Existing guest user logged in: {UserId} with GuestId: {GuestId}", user.Id, providerUserId);
5656
}
5757
}
58-
// 실제 OAuth 프로바이더인 경우 (Google, GitHub 등)
59-
else if (provider == "google" || provider == "github" || provider == "microsoft")
58+
// 실제 OAuth 프로바이더인 경우 (Google, Apple 등)
59+
else if (provider == "google" || provider == "apple")
6060
{
6161
if (string.IsNullOrEmpty(providerUserId))
6262
{
63-
throw new ValidationException(ErrorCode.PROVIDER_USER_ID_INVALID, "OAuth 제공자 사용자 ID가 필요합니다");
63+
throw new ValidationException(ErrorCode.PROVIDER_USER_ID_INVALID);
6464
}
6565

6666
// 새로운 사용자 ID 생성
@@ -79,7 +79,7 @@ public async Task<AuthResult> LoginWithOAuthAsync(string provider, string provid
7979
}
8080
else
8181
{
82-
throw new ValidationException(ErrorCode.OAUTH2_PROVIDER_NOT_SUPPORTED, $"지원하지 않는 OAuth 제공자입니다: {provider}");
82+
throw new ValidationException(ErrorCode.OAUTH2_PROVIDER_NOT_SUPPORTED);
8383
}
8484

8585
// OAuth2 사용자인 경우 Provider 정보를 포함하여 사용자 생성 (test와 guest는 이미 처리됨)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using ProjectVG.Application.Models.Auth;
2+
3+
namespace ProjectVG.Application.Services.Auth
4+
{
5+
/// <summary>
6+
/// OAuth2 제공자 인터페이스
7+
/// 각 OAuth2 제공자(Google, GitHub, Microsoft 등)는 이 인터페이스를 구현
8+
/// </summary>
9+
public interface IOAuth2Provider
10+
{
11+
/// <summary>
12+
/// 제공자 이름 (google, github, microsoft 등)
13+
/// </summary>
14+
string ProviderName { get; }
15+
16+
/// <summary>
17+
/// OAuth2 인증 URL 생성
18+
/// </summary>
19+
/// <param name="clientId">클라이언트 ID</param>
20+
/// <param name="redirectUri">리다이렉트 URI</param>
21+
/// <param name="state">상태값</param>
22+
/// <param name="codeChallenge">PKCE code challenge</param>
23+
/// <param name="codeChallengeMethod">PKCE challenge 방법</param>
24+
/// <param name="scopes">요청할 스코프</param>
25+
/// <returns>인증 URL</returns>
26+
string BuildAuthorizationUrl(string clientId, string redirectUri, string state, string codeChallenge, string codeChallengeMethod, string[] scopes);
27+
28+
/// <summary>
29+
/// 토큰 교환 엔드포인트 URL
30+
/// </summary>
31+
string TokenEndpoint { get; }
32+
33+
/// <summary>
34+
/// 사용자 정보 조회 엔드포인트 URL
35+
/// </summary>
36+
string UserInfoEndpoint { get; }
37+
38+
/// <summary>
39+
/// 기본 스코프 목록
40+
/// </summary>
41+
string[] DefaultScopes { get; }
42+
43+
/// <summary>
44+
/// 사용자 정보 응답을 표준 형식으로 변환
45+
/// </summary>
46+
/// <param name="jsonResponse">제공자별 JSON 응답</param>
47+
/// <returns>표준화된 사용자 정보</returns>
48+
OAuth2UserInfo ParseUserInfo(string jsonResponse);
49+
}
50+
}

ProjectVG.Application/Services/Auth/IOAuth2Service.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@ namespace ProjectVG.Application.Services.Auth
44
{
55
/// <summary>
66
/// OAuth2 인증 플로우 관리 서비스
7-
/// Google, GitHub, Microsoft 등의 외부 OAuth2 제공자와의 인증 처리를 담당
7+
/// Google, Apple 등의 외부 OAuth2 제공자와의 인증 처리를 담당
88
/// </summary>
99
public interface IOAuth2Service
1010
{
1111
/// <summary>
12-
/// OAuth2 인증 URL 생성 (PKCE 플로우 지원)
12+
/// 특정 제공자로 OAuth2 인증 URL 생성 (PKCE 플로우 지원)
1313
/// </summary>
14+
/// <param name="providerName">제공자 이름 (google, apple)</param>
1415
/// <param name="state">CSRF 보호를 위한 상태값</param>
1516
/// <param name="codeChallenge">PKCE code challenge</param>
1617
/// <param name="codeChallengeMethod">PKCE challenge 방법 (S256)</param>
1718
/// <param name="codeVerifier">PKCE code verifier</param>
1819
/// <param name="clientRedirectUri">클라이언트 리다이렉트 URI</param>
1920
/// <returns>OAuth2 인증 URL</returns>
20-
Task<string> BuildAuthorizationUrlAsync(string state, string codeChallenge, string codeChallengeMethod, string codeVerifier, string clientRedirectUri);
21+
Task<string> BuildAuthorizationUrlAsync(string providerName, string state, string codeChallenge, string codeChallengeMethod, string codeVerifier, string clientRedirectUri);
2122

2223
/// <summary>
2324
/// OAuth2 콜백 처리 (인증 코드를 토큰으로 교환하고 사용자 로그인 처리)
@@ -54,7 +55,7 @@ public interface IOAuth2Service
5455
/// OAuth2 제공자에서 사용자 정보 조회
5556
/// </summary>
5657
/// <param name="accessToken">OAuth2 액세스 토큰</param>
57-
/// <param name="provider">제공자명 (google, github, microsoft)</param>
58+
/// <param name="provider">제공자명 (google, apple)</param>
5859
/// <returns>사용자 정보</returns>
5960
Task<OAuth2UserInfo> GetUserInfoAsync(string accessToken, string provider);
6061

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using ProjectVG.Application.Services.Auth.Providers;
2+
using ProjectVG.Common.Exceptions;
3+
using ProjectVG.Common.Constants;
4+
5+
namespace ProjectVG.Application.Services.Auth
6+
{
7+
/// <summary>
8+
/// OAuth2 제공자 팩토리
9+
/// 제공자 이름에 따라 적절한 OAuth2 제공자 인스턴스를 생성
10+
/// </summary>
11+
public class OAuth2ProviderFactory : IOAuth2ProviderFactory
12+
{
13+
private readonly Dictionary<string, IOAuth2Provider> _providers;
14+
15+
public OAuth2ProviderFactory()
16+
{
17+
_providers = new Dictionary<string, IOAuth2Provider>(StringComparer.OrdinalIgnoreCase)
18+
{
19+
{ "google", new GoogleOAuth2Provider() },
20+
{ "apple", new AppleOAuth2Provider() }
21+
};
22+
}
23+
24+
/// <summary>
25+
/// 제공자 이름으로 OAuth2 제공자 인스턴스 조회
26+
/// </summary>
27+
/// <param name="providerName">제공자 이름 (google, apple)</param>
28+
/// <returns>OAuth2 제공자 인스턴스</returns>
29+
/// <exception cref="ValidationException">지원하지 않는 제공자인 경우</exception>
30+
public IOAuth2Provider GetProvider(string providerName)
31+
{
32+
if (string.IsNullOrEmpty(providerName)) {
33+
throw new ValidationException(ErrorCode.OAUTH2_PROVIDER_NOT_CONFIGURED);
34+
}
35+
36+
if (!_providers.TryGetValue(providerName, out var provider)) {
37+
throw new ValidationException(ErrorCode.OAUTH2_PROVIDER_NOT_SUPPORTED);
38+
}
39+
40+
return provider;
41+
}
42+
43+
/// <summary>
44+
/// 지원하는 모든 제공자 목록 조회
45+
/// </summary>
46+
/// <returns>지원하는 제공자 이름 목록</returns>
47+
public IEnumerable<string> GetSupportedProviders()
48+
{
49+
return _providers.Keys;
50+
}
51+
52+
/// <summary>
53+
/// 제공자가 지원되는지 확인
54+
/// </summary>
55+
/// <param name="providerName">확인할 제공자 이름</param>
56+
/// <returns>지원 여부</returns>
57+
public bool IsProviderSupported(string providerName)
58+
{
59+
return !string.IsNullOrEmpty(providerName) && _providers.ContainsKey(providerName);
60+
}
61+
}
62+
63+
/// <summary>
64+
/// OAuth2 제공자 팩토리 인터페이스
65+
/// </summary>
66+
public interface IOAuth2ProviderFactory
67+
{
68+
/// <summary>
69+
/// 제공자 이름으로 OAuth2 제공자 인스턴스 조회
70+
/// </summary>
71+
/// <param name="providerName">제공자 이름</param>
72+
/// <returns>OAuth2 제공자 인스턴스</returns>
73+
IOAuth2Provider GetProvider(string providerName);
74+
75+
/// <summary>
76+
/// 지원하는 모든 제공자 목록 조회
77+
/// </summary>
78+
/// <returns>지원하는 제공자 이름 목록</returns>
79+
IEnumerable<string> GetSupportedProviders();
80+
81+
/// <summary>
82+
/// 제공자가 지원되는지 확인
83+
/// </summary>
84+
/// <param name="providerName">확인할 제공자 이름</param>
85+
/// <returns>지원 여부</returns>
86+
bool IsProviderSupported(string providerName);
87+
}
88+
}

0 commit comments

Comments
 (0)