- 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>
175 lines
6.4 KiB
C#
175 lines
6.4 KiB
C#
using BaronSoftware.Auth.Sample;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using System.Windows;
|
|
|
|
namespace BaronSoftware.SSO.Sample
|
|
{
|
|
/// <summary>
|
|
/// Interaction logic for MainWindow.xaml
|
|
/// </summary>
|
|
public partial class MainWindow : Window
|
|
{
|
|
private BaronSSO _license;
|
|
private readonly SampleSettings _settings;
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
|
|
_settings = SampleSettings.Load();
|
|
|
|
var option = new BaronSSOOption()
|
|
{
|
|
Authority = _settings.Oidc.Authority,
|
|
ClientId = _settings.Oidc.ClientId,
|
|
RedirectUri = _settings.Oidc.RedirectUri,
|
|
ExtraUserValidator = new SimpleUserValidator()
|
|
};
|
|
|
|
_license = new BaronSSO(option);
|
|
LoginButton.Click += async (s,e) => await RunLogin("웹뷰 로그인", () => _license.SignInAsync());
|
|
TokenLoginBuggon.Click += async (s,e) => await RunLogin("토큰 로그인", () => _license.SignInAsync(_license.CurrentUser.RefreshToken));
|
|
SettingsButton.Click += SettingsButton_Click;
|
|
LogoutButton.Click += LogoutButton_Click;
|
|
|
|
}
|
|
|
|
private void SettingsButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var dlg = new SettingsWindow(_settings.Oidc.Authority, _settings.Oidc.ClientId, _settings.Oidc.RedirectUri, _settings.Oidc.LogoutUri)
|
|
{
|
|
Owner = this
|
|
};
|
|
if (dlg.ShowDialog() != true) return;
|
|
|
|
_settings.Oidc.Authority = dlg.Authority;
|
|
_settings.Oidc.ClientId = dlg.ClientId;
|
|
_settings.Oidc.RedirectUri = dlg.RedirectUri;
|
|
_settings.Oidc.LogoutUri = dlg.LogoutUri;
|
|
|
|
try
|
|
{
|
|
_settings.Save();
|
|
|
|
var option = new BaronSSOOption()
|
|
{
|
|
Authority = _settings.Oidc.Authority,
|
|
ClientId = _settings.Oidc.ClientId,
|
|
RedirectUri = _settings.Oidc.RedirectUri,
|
|
ExtraUserValidator = new SimpleUserValidator()
|
|
};
|
|
|
|
_license = new BaronSSO(option);
|
|
|
|
OutputBox.Text =
|
|
"설정 저장 완료 ✔ (appsettings.json)\n\n" +
|
|
$"Authority : {_settings.Oidc.Authority}\n" +
|
|
$"ClientId : {_settings.Oidc.ClientId}\n" +
|
|
$"RedirectUri : {_settings.Oidc.RedirectUri}\n" +
|
|
$"LogoutUri : {_settings.Oidc.LogoutUri}\n\n" +
|
|
"‘웹뷰로 로그인’으로 적용된 설정을 테스트하세요.";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
OutputBox.Text = $"설정 저장 실패:\n{ex.Message}";
|
|
}
|
|
}
|
|
|
|
|
|
private async void LogoutButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
SetBusy(true);
|
|
OutputBox.Text = "로그아웃 중... (토큰 + 세션 쿠키 삭제)";
|
|
try
|
|
{
|
|
await _license.SignOutAsync();
|
|
OutputBox.Text = "로그아웃 완료 ✔\n" +
|
|
"- refresh_token 삭제\n" +
|
|
"- WebView SSO 세션 쿠키 삭제\n" +
|
|
"→ 다음 ‘웹뷰로 로그인’ 시 로그인 창이 다시 표시됩니다.";
|
|
}
|
|
finally
|
|
{
|
|
SetBusy(false);
|
|
}
|
|
}
|
|
|
|
private async Task RunLogin(string action, Func<Task> work)
|
|
{
|
|
SetBusy(true);
|
|
OutputBox.Text = $"{action} 진행 중...";
|
|
try
|
|
{
|
|
await work();
|
|
OutputBox.Text = $"{action} 완료 ✔\n\n";
|
|
OutputBox.Text += ShowLoginUserInfo(_license.CurrentUser);
|
|
OutputBox.CaretIndex = 0; // 맨 위(요약)부터 보이도록
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
OutputBox.Text = $"{action} 취소됨 (로그인 창이 닫힘).";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
OutputBox.Text = $"{action} 실패:\n{ex.GetType().Name}: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
SetBusy(false);
|
|
}
|
|
}
|
|
|
|
private void SetBusy(bool busy)=> LoginButton.IsEnabled = LogoutButton.IsEnabled = !busy;
|
|
|
|
// For Display=============
|
|
private string ShowLoginUserInfo(UserInfo u)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("로그인 성공 ✔");
|
|
sb.AppendLine($"UserId(sub) : {u.UUID}");
|
|
sb.AppendLine($"Name : {u.Name}");
|
|
sb.AppendLine($"Email : {u.Email}");
|
|
sb.AppendLine($"TenantIds : {string.Join(", ", u.AllTenantIds ?? Array.Empty<string>())}");
|
|
sb.AppendLine($"Last Auth Time : {u.LastAuthTime}");
|
|
sb.AppendLine($"Claims Start------------ \n ");
|
|
sb.AppendLine(string.Join("\n", u.Claims.Select(kv => $" {kv.Key}: {kv.Value}")));
|
|
sb.AppendLine($"\nClaims End------------ \n ");
|
|
sb.AppendLine(!string.IsNullOrWhiteSpace(u.RefreshToken)
|
|
? "자동 로그인 : 사용 가능 (refresh_token 저장됨)"
|
|
: "자동 로그인 : 불가 — 리프레시 토큰 없음");
|
|
|
|
sb.AppendLine();
|
|
sb.AppendLine("==== token 엔드포인트 응답 (원본) ====");
|
|
sb.AppendLine(PrettyJson(u.RawTokenResponse));
|
|
|
|
sb.AppendLine();
|
|
sb.AppendLine("==== userinfo 응답 ====");
|
|
sb.AppendLine(PrettyJson(u.Raw));
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static readonly JsonSerializerOptions PrettyJsonOptions = new()
|
|
{
|
|
WriteIndented = true,
|
|
// 한글 등 비ASCII를 \uXXXX로 이스케이프하지 않고 그대로 표시
|
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
|
};
|
|
|
|
/// <summary>JSON 문자열을 들여쓰기(pretty) 형태로 변환. 파싱 실패 시 원본 반환.</summary>
|
|
private static string PrettyJson(string json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json)) return json;
|
|
try { return JsonNode.Parse(json)?.ToJsonString(PrettyJsonOptions) ?? json; }
|
|
catch { return json; }
|
|
}
|
|
|
|
// ================
|
|
|
|
|
|
|
|
}
|
|
}
|