주기적인 인증 기능 포함 및 코드 정리

This commit is contained in:
2026-06-25 13:50:54 +09:00
parent f178d69a99
commit ee38072ff9
14 changed files with 94 additions and 136 deletions

View File

@@ -1,3 +1,8 @@
using FluentScheduler;
using System.ComponentModel;
using System.Net.Http;
using System.Net.NetworkInformation;
namespace BaronSoftware.SSO
{
/// <summary>
@@ -7,6 +12,7 @@ namespace BaronSoftware.SSO
{
private readonly SsoClient client;
private readonly BaronSSOOption option;
private Schedule licenseCheckScheduler;
public UserInfo CurrentUser { get; private set; }
@@ -76,13 +82,13 @@ namespace BaronSoftware.SSO
/// UI 스레드에서 <c>.Wait()</c>/<c>.Result</c> 로 블로킹하면 데드락입니다. 자세한 이유는
/// <see cref="SignInAsync()"/> 의 설명을 참고하세요. (UI 스레드면 await, 동기 호출은 <see cref="SignIn"/>)
/// </remarks>
public async Task SignInAsync(string refreshToken)
public async Task SignInAsync(string refreshToken, bool retryWindow = true)
{
UserInfo user = null;
var isConnected = await CheckConnection();
if (isConnected)
{
var token = await client.LoginAsync(refreshToken);
var token = await client.LoginAsync(refreshToken, retryWindow);
var userJson = await client.GetUserInfoAsync(token.AccessToken);
if (string.IsNullOrEmpty(userJson))
throw new InvalidOperationException($"Failed to get userinfo : {token.ToString()}");
@@ -98,7 +104,7 @@ namespace BaronSoftware.SSO
user = UserInfo.FromSsoFile();
if (user == null)
throw new InvalidUserException("Not found sso data for offline.");
throw new InvalidUserException("Offline mode. Not found sso data for offline.");
}
// 가족사가 아니고, 인증도 실패 시
@@ -109,8 +115,12 @@ namespace BaronSoftware.SSO
CurrentUser = user;
CurrentUser.Save();
RunLicenseInterval();
}
/// <summary>
/// 로그아웃: 현재 사용자 정보 + 저장된 refresh_token + WebView SSO 세션 쿠키를 모두 삭제합니다.
/// 세션 쿠키까지 지우므로 다음 로그인 시 로그인 창이 다시 표시됩니다.
@@ -140,11 +150,59 @@ namespace BaronSoftware.SSO
await client.GetDiscoveryAsync();
return true;
}
catch
catch(Exception ex)
{
return false;
}
}
private void RunLicenseInterval()
{
if (licenseCheckScheduler != null)
return;
licenseCheckScheduler = new Schedule(async () =>
{
// 오프라인의 경우 주기적인 인증을 무시한다.
// 단, 언제든 온라인 상태가 될 수 있으니 주기 인증을 멈추지는 않는다.
var isOnline = await CheckConnection();
if (!isOnline)
return;
try
{
await SignInAsync(CurrentUser.RefreshToken, false);
}
catch(HttpRequestException)
{
//네트워크 오류는 무시한다.
}
catch(Exception ex)
{
StopLicenseInterval();
await SignOutAsync();
option.ExpiredToken?.Invoke();
}
}, run => run.Every(option.LicenseCheckInterval).Seconds()
);
licenseCheckScheduler.Start();
}
public void StopLicenseInterval()
{
if (licenseCheckScheduler == null)
return;
try
{
licenseCheckScheduler.Stop();
}
catch
{
}
licenseCheckScheduler = null;
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Globalization;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
namespace BaronSoftware.SSO
{
@@ -38,6 +39,7 @@ namespace BaronSoftware.SSO
/// </summary>
public bool EnableAutoLogin { get; set; } = true;
private IUserValidator familyValidator;
public IUserValidator FamilyValidator
{
@@ -49,5 +51,24 @@ namespace BaronSoftware.SSO
/// 사용자 인증에 대한 추가 검증이 필요한 경우, IUserValidator 인터페이스를 구현하여 Validator 속성에 할당할 수 있습니다.
/// </summary>
public required IUserValidator? ExtraUserValidator { get; set; }
private int licenseCheckInterval = 60;
/// <summary>
/// 주기적인 라이센스 확인 (초)
/// 최소 값은 30초 입니다.
/// </summary>
public int LicenseCheckInterval
{
get => licenseCheckInterval;
set
{
if (value < 15)
licenseCheckInterval = 15;
licenseCheckInterval = value;
}
}
public Action ExpiredToken { get; set; }
}
}

View File

@@ -9,6 +9,7 @@
<ItemGroup>
<!-- 임베디드 웹뷰 (Edge/Chromium 기반) -->
<PackageReference Include="FluentScheduler" Version="6.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.*" />
<!-- OIDC Discovery / JWKS / id_token 서명검증 -->
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.*" />

View File

@@ -74,9 +74,9 @@ namespace BaronSoftware.SSO
/// refreshToken 기반 인증: 저장된 refresh_token으로 창 없이(無窓) 토큰을 재발급한다.
/// refresh_token이 만료/무효 등으로 실패하면 웹뷰 대화형 로그인(LoginWindow)으로 폴백한다.
/// </summary>
public async Task<TokenResponse> LoginAsync(string refreshToken)
public async Task<TokenResponse> LoginAsync(string refreshToken, bool retryWindow = true)
{
if (string.IsNullOrWhiteSpace(refreshToken))
if (string.IsNullOrWhiteSpace(refreshToken) && retryWindow)
return await LoginAsync();
try
@@ -91,9 +91,9 @@ namespace BaronSoftware.SSO
}
catch
{
// 저장된 refresh_token이 만료/폐기/무효 → 자동 로그인 실패.
// 사용자가 다시 로그인할 수 있도록 웹뷰 로그인 창을 띄운다.
return await LoginAsync();
if(retryWindow)
return await LoginAsync();
throw;
}
}