SSO 로그아웃(RP-Initiated end_session) 구현 및 세션 쿠키 정리 보강
- BaronSSO.SignOutAsync: id_token_hint 기반 end_session 로그아웃 + 로컬 세션 정리 - SsoClient.LogoutAsync: end_session_endpoint 이동 후 post_logout 복귀를 WebView에서 가로채기 - BaronSSOOption.PostLogoutRedirectUri 추가 - LoginWindow: 쿠키 삭제를 ClearBrowsingDataAsync(완료 대기)로 변경해 재로그인 자동 SSO 통과 방지 - UserInfo: IsFamily/IsCenter 대소문자 무시 비교를 StringComparer로 수정(빌드 오류 해소) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BaronSoftware.SSO
|
||||
{
|
||||
@@ -16,12 +17,16 @@ namespace BaronSoftware.SSO
|
||||
public string Name { get; private set; }
|
||||
public string Email { get; private set; }
|
||||
public string[] SubEmails { get; private set; }
|
||||
public bool IsFamily { get; private set; }
|
||||
public string RefreshToken { get; private set; }
|
||||
public string TenantId { get; private set; }
|
||||
public string[] JoinedTenantIds { get; private set; }
|
||||
|
||||
public DateTime LastAuthTime {get; private set; }
|
||||
/// <summary>'tenants' 클레임 안에 등장하는 모든 테넌트 id 목록(상위/조상 테넌트 포함, 중복 제거).</summary>
|
||||
public string[] AllTenantIds { get; private set; }
|
||||
|
||||
public long LastAuthUnixTimeStamp { get; private set; }
|
||||
public DateTime LastAuthTime => DateTimeOffset.FromUnixTimeSeconds(LastAuthUnixTimeStamp).LocalDateTime;
|
||||
|
||||
public string IdToken { get; private set; }
|
||||
public string Raw { get; private set; }
|
||||
public string RawTokenResponse { get; private set; }
|
||||
public Dictionary<string, object> Claims { get; private set; }
|
||||
@@ -32,17 +37,40 @@ namespace BaronSoftware.SSO
|
||||
var result = new UserInfo();
|
||||
result.Raw = rawjson;
|
||||
result.RawTokenResponse = reposeToken.RawJson;
|
||||
result.IdToken = reposeToken.IdToken;
|
||||
result.RefreshToken = reposeToken.RefreshToken ?? "";
|
||||
result.UUID = userjson["sub"]?.GetValue<string>() ?? "";
|
||||
result.Name = userjson["name"]?.GetValue<string>() ?? "";
|
||||
result.Email = userjson["email"]?.GetValue<string>() ?? "";
|
||||
//result.SubEmails = userjson["sub_emails"]?.AsArray().Select(x => x.GetValue<string>()).ToArray() ?? Array.Empty<string>();
|
||||
result.TenantId = userjson["tenant_id"]?.GetValue<string>() ?? "";
|
||||
result.JoinedTenantIds = userjson["joined_tenants"]?.AsArray().Select(x => x.GetValue<string>()).ToArray() ?? Array.Empty<string>();
|
||||
result.AllTenantIds = ExtractTenantIds(userjson);
|
||||
result.Claims = userjson["rp_claims"]?.AsObject().ToDictionary(x => x.Key, x => (object)x.Value["value"]) ?? new Dictionary<string, object>();
|
||||
result.LastAuthTime = DateTimeOffset.FromUnixTimeSeconds(userjson["auth_time"]?.GetValue<long>() ?? 0).DateTime;
|
||||
result.LastAuthUnixTimeStamp = userjson["auth_time"]?.GetValue<long>() ?? 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 'tenants' 객체 안에 등장하는 모든 테넌트 id를 정규식으로 수집한다(상위/조상 포함, 중복 제거).
|
||||
/// </summary>
|
||||
private static string[] ExtractTenantIds(JsonNode userjson)
|
||||
{
|
||||
// 'tenants' 하위의 모든 "id":"..." 값을 뽑는 정규식 (상위 테넌트 + ancestors 포함)
|
||||
Regex regex = new("\"id\"\\s*:\\s*\"([^\"]+)\"", RegexOptions.Compiled);
|
||||
var tenantsJson = userjson?["tenants"]?.ToJsonString();
|
||||
if (string.IsNullOrEmpty(tenantsJson))
|
||||
return Array.Empty<string>();
|
||||
|
||||
var ids = new List<string>();
|
||||
var seen = new HashSet<string>();
|
||||
foreach (Match m in regex.Matches(tenantsJson))
|
||||
{
|
||||
var id = m.Groups[1].Value;
|
||||
if (!string.IsNullOrEmpty(id) && seen.Add(id))
|
||||
ids.Add(id);
|
||||
}
|
||||
return ids.ToArray();
|
||||
}
|
||||
|
||||
//// root = Ory Hydra id_token payload 또는 /userinfo 응답.
|
||||
//// 클레임은 (Descope의 customAttributes 하위가 아니라) 최상위에 평탄하게 들어오며,
|
||||
//// 상세 항목은 profile 스코프가 동의되었을 때만 포함됩니다.
|
||||
@@ -61,7 +89,7 @@ namespace BaronSoftware.SSO
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(GlobalConfigs.SsoFilePath));
|
||||
|
||||
File.WriteAllBytes(GlobalConfigs.SsoFilePath, protectedBytes);
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal static UserInfo? FromSsoFile()
|
||||
@@ -99,5 +127,15 @@ namespace BaronSoftware.SSO
|
||||
if (File.Exists(GlobalConfigs.SsoFilePath))
|
||||
File.Delete(GlobalConfigs.SsoFilePath);
|
||||
}
|
||||
|
||||
public bool IsCenter()
|
||||
{
|
||||
return AllTenantIds.Contains(GlobalConfigs.CenterTanant_UUID, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public bool IsFamily()
|
||||
{
|
||||
return AllTenantIds.Contains(GlobalConfigs.FamilyTanant_UUID, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user