- IUserValidator.Validate: bool 반환 → void (인증 실패 시 예외로 처리) - BaronSSOOption: Validator → ExtraUserValidator로 명명, FamilyValidator(기본 DefaultFamilyUserValidator) 추가 - DefaultFamilyUserValidator 신규: Center/Family 테넌트 사용자 통과, 그 외 InvalidUserException - BaronSSO.SignInAsync: Family/Extra 검증기 적용 흐름 정리 - InvalidUserException: UserInfo 기반 생성자 - Sample(MainWindow/SampleSettings/SimpleUserValidator) 갱신 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
3.7 KiB
C#
114 lines
3.7 KiB
C#
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()
|
|
{
|
|
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();
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|