Files
RhythmicWallpaper/AudioVisualizer/SSO/AuthManager.cs

281 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using IdentityModel.OidcClient;
using IdentityModel.OidcClient.Results;
using System.Net.Http.Headers;
using System.Text.Json;
using AudioWallpaper.Sso.Exceptions;
namespace AudioWallpaper.SSO {
public class AuthManager {
private OidcClient _oidcClient;
private readonly AuthConfig authConfig = new();
public static readonly AuthManager Instance = new AuthManager();
private AuthStatus authStatus = AuthStatus.Unknown;
//登录成功回调
public Action<TokenSet>? OnLoginSuccess;
//登录失败回调
public Action<string>? OnLoginFailed;
//刷新令牌成功回调
public Action<TokenSet>? OnRefreshTokenSuccess;
//刷新令牌失败回调
public Action<string>? OnRefreshTokenFailed;
//获取用户信息成功回调
public Action<UserInfoSet>? OnGetUserInfoSuccess;
//获取用户信息失败回调
public Action<string>? OnGetUserInfoFailed;
//撤销令牌成功回调
public Action? OnRevokeTokenSuccess;
//撤销令牌失败回调
public Action<string>? OnRevokeTokenFailed;
//注销登录成功回调
public Action? OnLogoutSuccess;
//注销登录失败回调
public Action<string>? OnLogoutFailed;
//登录状态改变回调
public Action<AuthStatus> OnAuthStatusChanged;
public AuthStatus AuthStatus {
get => authStatus; set {
authStatus = value;
OnAuthStatusChanged?.Invoke(value);
}
}
public AuthManager() {
var options = new OidcClientOptions {
Authority = authConfig.authorityUrl,
ClientId = authConfig.clientId,
RedirectUri = authConfig.redirectUri.ToString(),
Scope = authConfig.scope,
Policy = new Policy {
RequireAccessTokenHash = false,
},
LoadProfile = true
};
_oidcClient = new OidcClient(options);
Console.WriteLine("OIDC 客户端初始化");
}
public async void Login(Form? from = null) {
if (from == null) {
OnLoginFailed?.Invoke("登录窗口未初始化");
return;
}
if (_oidcClient == null) {
OnLoginFailed?.Invoke("OIDC客户端未初始化");
return;
}
if (AuthStatus == AuthStatus.Success) {
OnLoginFailed?.Invoke("已登录,请先注销");
return;
}
AuthStatus = AuthStatus.InProgress;
_oidcClient.Options.Browser = new WebView2Browser(from);
try {
var result = await _oidcClient.LoginAsync(new LoginRequest());
if (result.IsError) {
AuthStatus = AuthStatus.Failed;
OnLoginFailed?.Invoke(result.Error);
} else {
TokenSet tokenSet = new(
result.AccessToken,
result.RefreshToken,
result.IdentityToken
);
SsoException? exception = CheckToken(tokenSet);
if (exception != null) {
throw exception;
}
AuthStatus = AuthStatus.Success;
OnLoginSuccess?.Invoke(tokenSet);
}
} catch (Exception ex) {
AuthStatus = AuthStatus.Failed;
OnLoginFailed?.Invoke("登录异常:" + ex.Message);
}
}
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="accessToken">访问令牌</param>
/// <returns>用户信息</returns>
public UserInfoSet? GetUserInfo(string? accessToken) {
try {
if (string.IsNullOrWhiteSpace(accessToken)) {
throw new SsoException("AccessToken 为空");
}
using var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, authConfig.userInfoEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = httpClient.Send(request);
if (!response.IsSuccessStatusCode) {
OnGetUserInfoFailed?.Invoke($"获取用户信息失败,状态码: {response.StatusCode}");
return null;
}
var content = response.Content.ReadAsStringAsync().Result;
var userInfoSet = JsonSerializer.Deserialize<UserInfoSet>(content);
if (userInfoSet == null) {
throw new SsoException("获取用户信息失败,内容解析失败");
}
OnGetUserInfoSuccess?.Invoke(userInfoSet);
return userInfoSet;
} catch (Exception ex) {
OnGetUserInfoFailed?.Invoke($"获取用户信息异常: {ex.Message}");
return null;
}
}
/// <summary>
/// 刷新令牌
/// </summary>
/// <param name="refreshToken">刷新令牌</param>
/// <returns>令牌信息</returns>
public async Task<TokenSet?> RefreshToken(string? refreshToken) {
try {
if (_oidcClient == null) {
OnRefreshTokenFailed?.Invoke("OidcClient 未初始化");
return null;
}
if (string.IsNullOrWhiteSpace(refreshToken)) {
OnRefreshTokenFailed?.Invoke("刷新令牌失败refreshToken 不能为空");
return null;
}
// 调用 OidcClient 的刷新令牌方法
RefreshTokenResult result = await _oidcClient.RefreshTokenAsync(refreshToken);
if (result.IsError) {
OnRefreshTokenFailed?.Invoke($"刷新令牌失败: {result.Error}");
return null;
}
var tokenSet = new TokenSet(
result.AccessToken,
result.RefreshToken,
result.IdentityToken
);
SsoException? exception = CheckToken(tokenSet);
if (exception != null) {
throw exception;
}
AuthStatus = AuthStatus.Success;
OnRefreshTokenSuccess?.Invoke(tokenSet);
return tokenSet;
} catch (Exception ex) {
OnRefreshTokenFailed?.Invoke($"刷新令牌异常: {ex.Message}");
return null;
}
}
/// <summary>
/// 撤销令牌
/// </summary>
/// <param name="token">token</param>
/// <param name="tokenTypeHint">token id</param>
public async void RevokeToken(string? token, string? tokenTypeHint, bool form = false) {
if (string.IsNullOrWhiteSpace(token)) {
OnRevokeTokenFailed?.Invoke("撤销令牌失败token 不能为空");
if (form) {
OnLogoutFailed?.Invoke("token 不能为空");
}
return;
}
if (string.IsNullOrWhiteSpace(tokenTypeHint)) {
OnRevokeTokenFailed?.Invoke("撤销令牌失败tokenTypeHint 不能为空");
if (form) {
OnLogoutFailed?.Invoke("tokenTypeHint 不能为空");
}
return;
}
try {
using var httpClient = new HttpClient();
var parameters = new Dictionary<string, string>
{
{ "token", token }
};
if (!string.IsNullOrWhiteSpace(tokenTypeHint)) {
parameters.Add("token_type_hint", tokenTypeHint);
}
parameters.Add("client_id", authConfig.clientId);
// 如果有 client_secret说明是 confidential client
var clientSecretProperty = authConfig.GetType().GetProperty("clientSecret");
if (clientSecretProperty != null) {
var clientSecret = clientSecretProperty.GetValue(authConfig) as string;
if (!string.IsNullOrWhiteSpace(clientSecret)) {
parameters.Add("client_secret", clientSecret);
}
}
var content = new FormUrlEncodedContent(parameters);
var response = await httpClient.PostAsync(authConfig.revocationEndpoint, content);
var respContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode) {
// 撤销令牌成功,修改登录状态
AuthStatus = AuthStatus.LoginOut;
OnRevokeTokenSuccess?.Invoke();
if (form) {
OnLogoutSuccess?.Invoke();
}
return;
} else {
OnRevokeTokenFailed?.Invoke($"撤销令牌失败,状态码: {response.StatusCode},内容: {respContent}");
if (form) {
OnLogoutFailed?.Invoke($"撤销令牌失败,状态码: {response.StatusCode},内容: {respContent}");
}
return;
}
} catch (Exception ex) {
OnRevokeTokenFailed?.Invoke($"撤销令牌异常: {ex.Message}");
if (form) {
OnLogoutFailed?.Invoke($"撤销令牌异常: {ex.Message}");
}
}
}
/// <summary>
/// 注销登录
/// </summary>
public void Logout(TokenSet? set) {
SsoException? exception = CheckToken(set);
if (exception != null) {
OnLogoutFailed?.Invoke(exception.Message);
return;
}
#pragma warning disable CS8602 // 解引用可能出现空引用。
RevokeToken(set.RefreshToken, set.IdToken, true);
#pragma warning restore CS8602 // 解引用可能出现空引用。
//TokenManager
}
/// <summary>
/// 检查 TokenSet 是否有效
/// </summary>
/// <param name="tokenSet">TokenSet 对象</param>
/// <returns>如果有效返回Null如果无效返回对应的Exception</returns>
public static SsoException? CheckToken(TokenSet? tokenSet) {
if (tokenSet == null) {
return new SsoException("TokenSet 为空");
}
if (string.IsNullOrWhiteSpace(tokenSet.AccessToken)) {
return new SsoException("AccessToken 为空");
}
if (string.IsNullOrWhiteSpace(tokenSet.RefreshToken)) {
return new SsoException("RefreshToken 为空");
}
if (string.IsNullOrWhiteSpace(tokenSet.IdToken)) {
return new SsoException("IdToken 为空");
}
return null;
}
}
}