Initial commit: BARON SSO 샘플 (WebView OIDC PKCE 인증 라이브러리 + 데모 앱)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
11
BaronSoftware.SSO/Features/LoginWindow/LoginWindow.xaml
Normal file
11
BaronSoftware.SSO/Features/LoginWindow/LoginWindow.xaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<Window x:Class="BaronSoftware.SSO.LoginWindow"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
||||
Title="BARON 로그인" Height="760" Width="520"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid>
|
||||
<wv2:WebView2 x:Name="webview" />
|
||||
</Grid>
|
||||
</Window>
|
||||
114
BaronSoftware.SSO/Features/LoginWindow/LoginWindow.xaml.cs
Normal file
114
BaronSoftware.SSO/Features/LoginWindow/LoginWindow.xaml.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System.Windows;
|
||||
using WebView2 = Microsoft.Web.WebView2.Wpf.WebView2;
|
||||
|
||||
namespace BaronSoftware.SSO
|
||||
{
|
||||
/// <summary>
|
||||
/// 인증 엔드포인트를 WebView2로 로드하고, redirect_uri로의 이동을 가로채
|
||||
/// authorization code가 담긴 콜백 URL을 반환하는 로그인 창.
|
||||
/// redirect_uri로 실제 페이지가 로드되기 전에 NavigationStarting에서 취소하므로,
|
||||
/// localhost에 실제 서버를 띄울 필요가 없습니다.
|
||||
/// </summary>
|
||||
internal partial class LoginWindow : Window
|
||||
{
|
||||
private readonly string _authorizeUrl;
|
||||
private readonly string _redirectUri;
|
||||
private readonly TaskCompletionSource<string> _tcs =
|
||||
new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
internal LoginWindow(string authorizeUrl, string redirectUri)
|
||||
{
|
||||
InitializeComponent();
|
||||
_authorizeUrl = authorizeUrl;
|
||||
_redirectUri = redirectUri;
|
||||
|
||||
Loaded += async (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
await webview.EnsureCoreWebView2Async();
|
||||
webview.CoreWebView2.NavigationStarting += OnNavigationStarting;
|
||||
webview.CoreWebView2.Navigate(_authorizeUrl);
|
||||
#else
|
||||
|
||||
// 1. 웹뷰 환경 설정 객체 생성
|
||||
var environment = await CoreWebView2Environment.CreateAsync(null, null, null);
|
||||
|
||||
// 2. 컨트롤러 옵션 생성 및 InPrivate 모드 활성화
|
||||
var options = environment.CreateCoreWebView2ControllerOptions();
|
||||
options.IsInPrivateModeEnabled = true; // 핵심 설정
|
||||
|
||||
// 3. 인프라이빗 옵션을 적용하여 WebView2 초기화
|
||||
await webview.EnsureCoreWebView2Async(environment, options);
|
||||
|
||||
webview.CoreWebView2.NavigationStarting += OnNavigationStarting;
|
||||
webview.CoreWebView2.Navigate(_authorizeUrl);
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_tcs.TrySetException(ex);
|
||||
Dispatcher.BeginInvoke(new Action(Close));
|
||||
}
|
||||
};
|
||||
|
||||
Closed += (_, _) => _tcs.TrySetException(new OperationCanceledException());
|
||||
}
|
||||
|
||||
private void OnNavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs e)
|
||||
{
|
||||
if (e.Uri.StartsWith(_redirectUri, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
e.Cancel = true; // 실제 localhost 로딩 차단
|
||||
_tcs.TrySetResult(e.Uri);
|
||||
Dispatcher.BeginInvoke(new Action(Close));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>창을 띄우고 콜백 URL(code 포함)을 비동기로 반환.</summary>
|
||||
internal Task<string> ShowAndGetRedirectAsync()
|
||||
{
|
||||
Show();
|
||||
return _tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 로그인 WebView가 사용하는 프로필의 모든 쿠키(=SSO 세션 쿠키)를 삭제합니다.
|
||||
/// 화면에 보이지 않는 오프스크린 WebView2를 잠깐 띄워 동일 프로필의 쿠키를 비웁니다.
|
||||
/// 이후 다음 로그인 시 세션이 없어 로그인 폼이 다시 표시됩니다.
|
||||
/// </summary>
|
||||
internal static async Task ClearSessionCookiesAsync()
|
||||
{
|
||||
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 확보(화면 밖)
|
||||
try
|
||||
{
|
||||
await web.EnsureCoreWebView2Async();
|
||||
// DeleteAllCookies()는 즉시 반환이라, 디스크 반영 전에 Dispose되면 쿠키가 남아
|
||||
// 다음 로그인이 SSO로 조용히 통과될 수 있다. 완료까지 await하는 ClearBrowsingDataAsync로
|
||||
// SSO 세션 쿠키/사이트 데이터를 확실히 제거한다.
|
||||
await web.CoreWebView2.Profile.ClearBrowsingDataAsync(
|
||||
CoreWebView2BrowsingDataKinds.AllSite);
|
||||
}
|
||||
finally
|
||||
{
|
||||
web.Dispose();
|
||||
holder.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
BaronSoftware.SSO/Features/Validator/IUserValidator.cs
Normal file
9
BaronSoftware.SSO/Features/Validator/IUserValidator.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace BaronSoftware.SSO
|
||||
{
|
||||
/// <summary>id_token을 검증(서명/발급자/대상/만료)하고 파싱된 JWT를 반환합니다.</summary>
|
||||
public interface IUserValidator
|
||||
{
|
||||
public void Validate(UserInfo user);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user