namespace BaronSoftware.SSO { /// /// baron-sso(Ory Hydra · OIDC PKCE)로 로그인하고 사용자 정보를 보관하는 바론의 인증모듈. /// public class BaronSSO { private readonly SsoClient client; private readonly BaronSSOOption option; public UserInfo CurrentUser { get; private set; } public BaronSSO(BaronSSOOption option) { if(option == null) throw new ArgumentNullException(nameof(option)); if (!string.IsNullOrWhiteSpace(option.Authority)) GlobalConfigs.SsoUri = option.Authority; client = SsoClient.Create(option); this.option = option; } public void SignIn() { // STA Thread 이슈로 인해, Task.Run() 으로 감싸서 동기화 함수 구현. Task.Run(async () => await SignInAsync()).Wait(); } public void SignOut() { Task.Run(async () => await SignOutAsync()).Wait(); } /// 웹뷰 로그인 창을 띄워 인증합니다. public async Task SignInAsync() { UserInfo? user = null; if (option.EnableAutoLogin) user = UserInfo.FromSsoFile(); await SignInAsync(user?.RefreshToken); } public async Task SignInAsync(string refreshToken) { UserInfo user = null; var isConnected = await CheckConnection(); if (isConnected) { var token = await client.LoginAsync(refreshToken); var userJson = await client.GetUserInfoAsync(token.AccessToken); if (string.IsNullOrEmpty(userJson)) throw new InvalidOperationException($"Failed to get userinfo : {token.ToString()}"); user = UserInfo.FromJson(userJson, token); if (user == null) throw new InvalidUserException($"Broken user token. Token {token.ToString()}, UserJson {userJson}"); } else { if (!option.EnableOffline) throw new InvalidOperationException("Network isn't available."); user = UserInfo.FromSsoFile(); if (user == null) throw new InvalidUserException("Not found sso data for offline."); } // 가족사가 아니고, 인증도 실패 시 if (user.IsFamily()) option?.FamilyValidator?.Validate(user); else option?.ExtraUserValidator?.Validate(user); CurrentUser = user; CurrentUser.Save(); } /// /// 로그아웃: 현재 사용자 정보 + 저장된 refresh_token + WebView SSO 세션 쿠키를 모두 삭제합니다. /// 세션 쿠키까지 지우므로 다음 로그인 시 로그인 창이 다시 표시됩니다. /// public async Task SignOutAsync() { try { //SSO 서버 세션 종료(RP-Initiated Logout) + 로컬 WebView 세션 쿠키 정리 UserInfo.Clear(); await client.LogoutAsync(CurrentUser?.IdToken); } catch { // 로그아웃 실패해도 로컬 사용자 정보는 이미 삭제했으므로 로컬 로그아웃으로 간주. } } private async Task CheckConnection() { try { await client.GetDiscoveryAsync(); return true; } catch { return false; } } } }