로그아웃 시 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

@@ -72,19 +72,29 @@ namespace BaronSoftware.SSO
/// <summary>
/// refreshToken 기반 인증: 저장된 refresh_token으로 창 없이(無窓) 토큰을 재발급한다.
/// refresh_token이 만료/무효 등으로 실패하면 웹뷰 대화형 로그인(LoginWindow)으로 폴백한다.
/// </summary>
public async Task<TokenResponse> LoginAsync(string refreshToken)
{
if (string.IsNullOrWhiteSpace(refreshToken))
return await LoginAsync();
var disc = await GetDiscoveryAsync();
return await ExchangeAsync(disc, new Dictionary<string, string>
try
{
["grant_type"] = "refresh_token",
["refresh_token"] = refreshToken,
["client_id"] = options.ClientId,
});
var disc = await GetDiscoveryAsync();
return await ExchangeAsync(disc, new Dictionary<string, string>
{
["grant_type"] = "refresh_token",
["refresh_token"] = refreshToken,
["client_id"] = options.ClientId,
});
}
catch
{
// 저장된 refresh_token이 만료/폐기/무효 → 자동 로그인 실패.
// 사용자가 다시 로그인할 수 있도록 웹뷰 로그인 창을 띄운다.
return await LoginAsync();
}
}
/// <summary>.well-known/openid-configuration 문서를 1회 로드 후 캐시.</summary>
@@ -121,15 +131,9 @@ namespace BaronSoftware.SSO
$"&post_logout_redirect_uri={Uri.EscapeDataString(options.PostLogoutRedirectUri)}" +
$"&state={PKCEUtil.RandomToken()}";
try
{
// 전용 STA 스레드에서 로그아웃(end_session) 창을 띄운다.
await LoginWindow.AuthenticateAsync(url, options.PostLogoutRedirectUri);
}
catch (OperationCanceledException)
{
// 사용자가 로그아웃 창을 닫아도 로컬 정리는 계속 진행한다.
}
// 화면 밖(오프스크린) WebView2로 end_session을 수행해
// 로그아웃 시 WebView 창/에러 페이지가 사용자에게 보이지 않게 한다.
await LoginWindow.EndSessionAsync(url, options.PostLogoutRedirectUri);
}
}
catch