Files
baron-sso-sample/BaronSoftware.SSO.Sample/MainWindow.xaml.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

156 lines
5.9 KiB
C#

using BaronSoftware;
using BaronSoftware.Auth;
using BaronSoftware.Auth.Sample;
using System;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
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();
ApplySettings();
}
/// <summary>현재 설정으로 SSO 클라이언트를 (재)생성한다.</summary>
private void ApplySettings() => _license = new BaronSSO(_settings.ToOidcOptions());
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();
ApplySettings();
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 LoginButton_Click(object sender, RoutedEventArgs e)
=> await RunAsync("웹뷰 로그인", () => _license.SignInAsync());
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 RunAsync(string action, Func<Task> work)
{
SetBusy(true);
OutputBox.Text = $"{action} 진행 중...";
try
{
await work();
OutputBox.Text = $"{action} 완료 ✔\n\n";
OutputBox.Text += Format(_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;
private string Format(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; }
}
private async void TokenLoginBuggon_Click(object sender, RoutedEventArgs e) => await RunAsync("토큰 로그인", () => _license.SignInAsync(_license.CurrentUser.RefreshToken));
}
}