diff --git a/AuthTest/App.xaml b/AuthTest/App.xaml
deleted file mode 100644
index 4cd93b6..0000000
--- a/AuthTest/App.xaml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
diff --git a/AuthTest/App.xaml.cs b/AuthTest/App.xaml.cs
deleted file mode 100644
index 983332d..0000000
--- a/AuthTest/App.xaml.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using BaronSoftware.SSO;
-using System.Configuration;
-using System.Data;
-using System.Threading.Tasks;
-using System.Windows;
-
-namespace AuthTest
-{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App : Application
- {
- protected override void OnStartup(StartupEventArgs e)
- {
- base.OnStartup(e);
-
- BaronSSOOption option = new()
- {
- Authority = "https://sso.hmac.kr/oidc",
- ClientId = "aca44872-8280-40c3-9a80-3aefafdf722a",
- RedirectUri = "http://localhost:9090/eg-bim/auth/callback",
- PostLogoutRedirectUri = "http://localhost:9090/eg-bim/logout/callback",
- ExtraUserValidator = new ExtraUserInvalidator()
- };
- var auth = new BaronSSO(option);
- auth.SignIn();
- }
- }
-
-}
diff --git a/AuthTest/AssemblyInfo.cs b/AuthTest/AssemblyInfo.cs
deleted file mode 100644
index b0ec827..0000000
--- a/AuthTest/AssemblyInfo.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Windows;
-
-[assembly: ThemeInfo(
- ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
- //(used if a resource is not found in the page,
- // or application resource dictionaries)
- ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
- //(used if a resource is not found in the page,
- // app, or any theme specific resource dictionaries)
-)]
diff --git a/AuthTest/AuthTest.csproj b/AuthTest/AuthTest.csproj
deleted file mode 100644
index 4a4e51e..0000000
--- a/AuthTest/AuthTest.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- WinExe
- net8.0-windows
- enable
- enable
- true
-
-
-
-
-
-
-
diff --git a/AuthTest/ExtraUserInvalidator.cs b/AuthTest/ExtraUserInvalidator.cs
deleted file mode 100644
index 456ca5a..0000000
--- a/AuthTest/ExtraUserInvalidator.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using BaronSoftware.SSO;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace AuthTest
-{
- public class ExtraUserInvalidator : IUserValidator
- {
- public void Validate(UserInfo user)
- {
- }
- }
-}
diff --git a/AuthTest/MainWindow.xaml b/AuthTest/MainWindow.xaml
deleted file mode 100644
index 669c1d9..0000000
--- a/AuthTest/MainWindow.xaml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
diff --git a/AuthTest/MainWindow.xaml.cs b/AuthTest/MainWindow.xaml.cs
deleted file mode 100644
index 5124d38..0000000
--- a/AuthTest/MainWindow.xaml.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Text;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-
-namespace AuthTest
-{
- ///
- /// Interaction logic for MainWindow.xaml
- ///
- public partial class MainWindow : Window
- {
- public MainWindow()
- {
- InitializeComponent();
- }
- }
-}
\ No newline at end of file
diff --git a/BaronSoftware.SSO.Sample/MainWindow.xaml.cs b/BaronSoftware.SSO.Sample/MainWindow.xaml.cs
index d395002..942a22d 100644
--- a/BaronSoftware.SSO.Sample/MainWindow.xaml.cs
+++ b/BaronSoftware.SSO.Sample/MainWindow.xaml.cs
@@ -25,7 +25,9 @@ namespace BaronSoftware.SSO.Sample
Authority = _settings.Oidc.Authority,
ClientId = _settings.Oidc.ClientId,
RedirectUri = _settings.Oidc.RedirectUri,
- ExtraUserValidator = new SimpleUserValidator()
+ ExtraUserValidator = new SimpleUserValidator(),
+ LicenseCheckInterval = 15,
+ ExpiredToken = () => { MessageBox.Show("토큰이 끊겼습니다. 다시 로그인 하세요"); },
};
_license = new BaronSSO(option);
diff --git a/BaronSoftware.SSO.Sample/appsettings.json b/BaronSoftware.SSO.Sample/appsettings.json
index 07fa915..e51af79 100644
--- a/BaronSoftware.SSO.Sample/appsettings.json
+++ b/BaronSoftware.SSO.Sample/appsettings.json
@@ -9,7 +9,7 @@
"Authority": "https://sso.hmac.kr/oidc",
// 퍼블릭 클라이언트 ID (콘솔 '앱 자격 증명' 화면)
- "ClientId": "4e4c88fc-2b0a-4b8b-b9b5-fb407cdbbfac",
+ "ClientId": "c072532b-3483-4539-a21f-e77a9f6e2c7a",
// 콘솔 '리디렉션 URI 설정'에 등록된 값과 문자 그대로 일치해야 함
"RedirectUri": "http://127.0.0.1:8421/baron-sample/auth/callback",
diff --git a/BaronSoftware.SSO.sln b/BaronSoftware.SSO.sln
index 360dc8d..b477436 100644
--- a/BaronSoftware.SSO.sln
+++ b/BaronSoftware.SSO.sln
@@ -7,8 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaronSoftware.SSO", "BaronS
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaronSoftware.SSO.Sample", "BaronSoftware.SSO.Sample\BaronSoftware.SSO.Sample.csproj", "{6F80A264-7099-3F9E-64A3-89E569A3E7A4}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthTest", "AuthTest\AuthTest.csproj", "{CC253CA9-0F0E-43D9-80D5-CEC2A181E889}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -23,10 +21,6 @@ Global
{6F80A264-7099-3F9E-64A3-89E569A3E7A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F80A264-7099-3F9E-64A3-89E569A3E7A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F80A264-7099-3F9E-64A3-89E569A3E7A4}.Release|Any CPU.Build.0 = Release|Any CPU
- {CC253CA9-0F0E-43D9-80D5-CEC2A181E889}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CC253CA9-0F0E-43D9-80D5-CEC2A181E889}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CC253CA9-0F0E-43D9-80D5-CEC2A181E889}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CC253CA9-0F0E-43D9-80D5-CEC2A181E889}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/BaronSoftware.SSO/BaronSSO.cs b/BaronSoftware.SSO/BaronSSO.cs
index a88e0c3..363a7d0 100644
--- a/BaronSoftware.SSO/BaronSSO.cs
+++ b/BaronSoftware.SSO/BaronSSO.cs
@@ -1,3 +1,8 @@
+using FluentScheduler;
+using System.ComponentModel;
+using System.Net.Http;
+using System.Net.NetworkInformation;
+
namespace BaronSoftware.SSO
{
///
@@ -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 스레드에서 .Wait()/.Result 로 블로킹하면 데드락입니다. 자세한 이유는
/// 의 설명을 참고하세요. (UI 스레드면 await, 동기 호출은 )
///
- 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();
}
+
+
///
/// 로그아웃: 현재 사용자 정보 + 저장된 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;
+ }
+
}
}
diff --git a/BaronSoftware.SSO/BaronSSOOption.cs b/BaronSoftware.SSO/BaronSSOOption.cs
index 1ab4bb8..f8f066d 100644
--- a/BaronSoftware.SSO/BaronSSOOption.cs
+++ b/BaronSoftware.SSO/BaronSSOOption.cs
@@ -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
///
public bool EnableAutoLogin { get; set; } = true;
+
private IUserValidator familyValidator;
public IUserValidator FamilyValidator
{
@@ -49,5 +51,24 @@ namespace BaronSoftware.SSO
/// 사용자 인증에 대한 추가 검증이 필요한 경우, IUserValidator 인터페이스를 구현하여 Validator 속성에 할당할 수 있습니다.
///
public required IUserValidator? ExtraUserValidator { get; set; }
+
+ private int licenseCheckInterval = 60;
+ ///
+ /// 주기적인 라이센스 확인 (초)
+ /// 최소 값은 30초 입니다.
+ ///
+ public int LicenseCheckInterval
+ {
+ get => licenseCheckInterval;
+ set
+ {
+ if (value < 15)
+ licenseCheckInterval = 15;
+ licenseCheckInterval = value;
+ }
+ }
+
+
+ public Action ExpiredToken { get; set; }
}
}
diff --git a/BaronSoftware.SSO/BaronSoftware.SSO.csproj b/BaronSoftware.SSO/BaronSoftware.SSO.csproj
index 30f1ed1..b6d499b 100644
--- a/BaronSoftware.SSO/BaronSoftware.SSO.csproj
+++ b/BaronSoftware.SSO/BaronSoftware.SSO.csproj
@@ -9,6 +9,7 @@
+
diff --git a/BaronSoftware.SSO/OIDC/SsoClient.cs b/BaronSoftware.SSO/OIDC/SsoClient.cs
index 989893d..d9f3017 100644
--- a/BaronSoftware.SSO/OIDC/SsoClient.cs
+++ b/BaronSoftware.SSO/OIDC/SsoClient.cs
@@ -74,9 +74,9 @@ namespace BaronSoftware.SSO
/// refreshToken 기반 인증: 저장된 refresh_token으로 창 없이(無窓) 토큰을 재발급한다.
/// refresh_token이 만료/무효 등으로 실패하면 웹뷰 대화형 로그인(LoginWindow)으로 폴백한다.
///
- public async Task LoginAsync(string refreshToken)
+ public async Task 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;
}
}