로그아웃 시 WebView 창/에러 페이지 노출 방지 (오프스크린 end_session)

- LoginWindow.EndSessionAsync 추가: 1×1 화면 밖 WebView2로 end_session을 수행해
  로그아웃 시 창이나 네비게이션 에러 페이지(ERR_ABORTED 등)가 보이지 않게 함.
  post_logout_redirect_uri 복귀를 NavigationStarting에서 가로채 종료하고,
  복귀를 못 가로채도 timeout 후 조용히 반환.
- SsoClient.LogoutAsync: 보이는 AuthenticateAsync 대신 EndSessionAsync 사용.
- BaronSSO.SignOutAsync: CurrentUser가 null이면 파일에서 로드 후 로그아웃.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 12:11:14 +09:00
parent dd2c4e4975
commit f178d69a99
3 changed files with 80 additions and 15 deletions

View File

@@ -109,6 +109,63 @@ namespace BaronSoftware.SSO
}
});
/// <summary>
/// SSO 로그아웃(end_session)을 화면 밖(오프스크린) WebView2에서 수행합니다.
/// 로그인 창과 달리 1×1 화면 밖 창을 쓰므로, 사용자에게 WebView 창이나
/// 네비게이션 에러 페이지(ERR_ABORTED 등)가 일절 보이지 않습니다.
/// end_session_endpoint를 로드하고 post_logout_redirect_uri로의 복귀를 가로채 종료하며,
/// 복귀를 끝내 가로채지 못해도(서버/네트워크 문제) timeoutMs 후 조용히 반환합니다.
/// </summary>
internal static Task EndSessionAsync(
string endSessionUrl, string postLogoutRedirectUri, int timeoutMs = 10000)
=> RunOnDedicatedUiThreadAsync(async () =>
{
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var holder = new Window
{
Width = 1,
Height = 1,
Left = -32000,
Top = -32000,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false,
Title = string.Empty,
};
var web = new WebView2();
holder.Content = web;
holder.Show(); // WebView2 초기화에 필요한 HWND 확보(화면 밖)
void OnNavStarting(object? s, CoreWebView2NavigationStartingEventArgs e)
{
if (e.Uri.StartsWith(postLogoutRedirectUri, StringComparison.OrdinalIgnoreCase))
{
e.Cancel = true; // 복귀 주소 실제 로딩 차단
tcs.TrySetResult(true);
}
}
try
{
await web.EnsureCoreWebView2Async();
web.CoreWebView2.NavigationStarting += OnNavStarting;
web.CoreWebView2.Navigate(endSessionUrl);
// 복귀를 가로채면 즉시 완료, 못 가로채도 timeout 후 조용히 종료한다.
await Task.WhenAny(tcs.Task, Task.Delay(timeoutMs));
}
catch
{
// 오프스크린이라 어떤 네비게이션 에러도 사용자에게 노출되지 않는다. 조용히 무시.
}
finally
{
web.Dispose();
holder.Close();
}
});
/// <summary>
/// 호출 스레드(MTA/STA)와 무관하게, 전용 STA 스레드에서 자체 Dispatcher 메시지펌프를 돌려
/// WPF/WebView2 UI 작업을 실행하고 그 결과를 Task로 반환한다.