로그인 STA 스레드화·SSO 로그아웃·UserInfo 역직렬화 수정 및 AuthTest 추가
- LoginWindow: 로그인/로그아웃/쿠키삭제를 전용 STA 스레드(자체 Dispatcher 펌프)에서 실행 → SignIn/SignInAsync가 MTA/STA 어느 스레드에서 호출돼도 동작 (RunOnDedicatedUiThreadAsync, AuthenticateAsync) - SsoClient: 로그인/로그아웃 창을 AuthenticateAsync로 호출, 크로스스레드 Owner 제거 - BaronSSO: SignIn/SignOut을 Task.Run(...).GetAwaiter().GetResult()로 정리, SignInAsync 데드락 주석 추가 - UserInfo: private set 프로퍼티에 [JsonInclude] 적용 → FromSsoFile 역직렬화 복원 정상화, LastAuthTime [JsonIgnore] - AuthTest 샘플 프로젝트 추가 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,16 +24,42 @@ namespace BaronSoftware.SSO
|
||||
|
||||
public void SignIn()
|
||||
{
|
||||
// STA Thread 이슈로 인해, Task.Run() 으로 감싸서 동기화 함수 구현.
|
||||
Task.Run(async () => await SignInAsync()).Wait();
|
||||
// UI 스레드에서 호출돼도 데드락이 없도록 Task.Run(스레드풀)에서 실행한다.
|
||||
// (실제 WebView 창은 SsoClient가 전용 STA 스레드에서 띄우므로 STA/MTA 무관하게 동작)
|
||||
// .GetAwaiter().GetResult()로 원본 예외를 그대로 전파한다(AggregateException 래핑 방지).
|
||||
Task.Run(() => SignInAsync()).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public void SignOut()
|
||||
{
|
||||
Task.Run(async () => await SignOutAsync()).Wait();
|
||||
Task.Run(() => SignOutAsync()).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>웹뷰 로그인 창을 띄워 인증합니다.</summary>
|
||||
/// <summary>
|
||||
/// 웹뷰 로그인 창을 띄워 인증합니다. (비동기)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ⚠️ 절대 UI 스레드에서 <c>.Wait()</c> / <c>.Result</c> 로 블로킹하지 마세요. 데드락이 발생합니다.
|
||||
///
|
||||
/// [데드락이 나는 이유 — sync-over-async]
|
||||
/// 1) WPF UI 스레드에는 DispatcherSynchronizationContext 가 설치돼 있습니다.
|
||||
/// 2) 이 메서드를 UI 스레드에서 호출하면, 내부의 첫 <c>await</c>
|
||||
/// (CheckConnection → GetDiscoveryAsync → httpclient.GetStringAsync 등)에서
|
||||
/// ConfigureAwait(false) 를 쓰지 않으므로 "연속(continuation)을 UI 스레드로 되돌려" 실행하도록
|
||||
/// 컨텍스트를 캡처합니다.
|
||||
/// 3) 그런데 호출 측이 <c>.Wait()</c> 로 UI 스레드를 막으면 메시지펌프가 멈춥니다.
|
||||
/// 4) await 대상(HTTP 등)이 스레드풀에서 끝나고 연속을 UI 디스패처 큐에 post 하지만,
|
||||
/// UI 스레드가 막혀 있어 그 큐를 영원히 처리하지 못합니다 → Task 미완료 → .Wait() 무한 대기 → 데드락.
|
||||
/// (로그인 창에 도달하기 전, 첫 HTTP await 에서 이미 멈춥니다.)
|
||||
///
|
||||
/// [올바른 사용법]
|
||||
/// • UI 스레드라면 블로킹하지 말고 await 하세요: <c>await auth.SignInAsync();</c>
|
||||
/// • 동기적으로 써야 하면 동기 진입점 <see cref="SignIn"/> 을 쓰세요.
|
||||
/// SignIn() 은 <c>Task.Run(() => SignInAsync()).GetAwaiter().GetResult()</c> 로,
|
||||
/// SignInAsync 를 SynchronizationContext 가 없는 스레드풀 스레드에서 실행하므로
|
||||
/// 연속이 UI 스레드로 되돌아올 필요가 없어 데드락이 발생하지 않습니다.
|
||||
/// (실제 WebView 로그인 창은 SsoClient 가 전용 STA 스레드에서 띄우므로 STA/MTA 무관하게 동작합니다.)
|
||||
/// </remarks>
|
||||
public async Task SignInAsync()
|
||||
{
|
||||
UserInfo? user = null;
|
||||
@@ -43,6 +69,13 @@ namespace BaronSoftware.SSO
|
||||
await SignInAsync(user?.RefreshToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// refresh_token 으로 무창 인증을 시도하고, 불가하면 웹뷰 로그인으로 진행합니다. (비동기)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// UI 스레드에서 <c>.Wait()</c>/<c>.Result</c> 로 블로킹하면 데드락입니다. 자세한 이유는
|
||||
/// <see cref="SignInAsync()"/> 의 설명을 참고하세요. (UI 스레드면 await, 동기 호출은 <see cref="SignIn"/>)
|
||||
/// </remarks>
|
||||
public async Task SignInAsync(string refreshToken)
|
||||
{
|
||||
UserInfo user = null;
|
||||
|
||||
Reference in New Issue
Block a user