Files
baron-sso-sample/BaronSoftware.SSO/BaronSSO.cs
최준영 1c41230021 SSO 로그아웃(RP-Initiated end_session) 구현 및 세션 쿠키 정리 보강
- BaronSSO.SignOutAsync: id_token_hint 기반 end_session 로그아웃 + 로컬 세션 정리
- SsoClient.LogoutAsync: end_session_endpoint 이동 후 post_logout 복귀를 WebView에서 가로채기
- BaronSSOOption.PostLogoutRedirectUri 추가
- LoginWindow: 쿠키 삭제를 ClearBrowsingDataAsync(완료 대기)로 변경해 재로그인 자동 SSO 통과 방지
- UserInfo: IsFamily/IsCenter 대소문자 무시 비교를 StringComparer로 수정(빌드 오류 해소)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:25:41 +09:00

108 lines
3.6 KiB
C#

using BaronSoftware.SSO.Exceptions;
namespace BaronSoftware.SSO
{
/// <summary>
/// baron-sso(Ory Hydra · OIDC PKCE)로 로그인하고 사용자 정보를 보관하는 바론의 인증모듈.
/// </summary>
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();
}
/// <summary>웹뷰 로그인 창을 띄워 인증합니다.</summary>
public async Task SignInAsync() => await SignInAsync(null);
/// <summary>웹뷰 로그인 창을 띄워 인증합니다.</summary>
public async Task SignInAsync(string refreshToken)
{
UserInfo user = null;
if(option.EnableAutoLogin)
user = UserInfo.FromSsoFile();
var isConnected = await CheckConnection();
if(!isConnected && option.EnableOffline)
{
if (user == null)
throw new InvalidUserException("Not found authorized last user.");
option?.Validator?.Validate(user);
return;
}
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 || option?.Validator == null)
throw new NullReferenceException("user or validator option is null");
// 가족사가 아니고, 인증도 실패 시
if(!user.IsFamily()&& !option.Validator.Validate(user))
throw new InvalidUserException("Failed to authorize user");
CurrentUser = user;
CurrentUser.Save();
}
/// <summary>
/// 로그아웃: 현재 사용자 정보 + 저장된 refresh_token + WebView SSO 세션 쿠키를 모두 삭제합니다.
/// 세션 쿠키까지 지우므로 다음 로그인 시 로그인 창이 다시 표시됩니다.
/// </summary>
public async Task SignOutAsync()
{
try
{
//SSO 서버 세션 종료(RP-Initiated Logout) + 로컬 WebView 세션 쿠키 정리
UserInfo.Clear();
await client.LogoutAsync(CurrentUser?.IdToken);
}
catch
{
// 로그아웃 실패해도 로컬 사용자 정보는 이미 삭제했으므로 로컬 로그아웃으로 간주.
}
}
private async Task<bool> CheckConnection()
{
try
{
await client.GetDiscoveryAsync();
return true;
}
catch
{
return false;
}
}
}
}