You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

108 lines
3.5 KiB
C#

using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace NPin.Framework.AspNetCore.Authentication.OAuth;
public abstract class OAuthAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions>
where TOptions : AuthenticationSchemeOptions, new()
{
public abstract string AuthenticationSchemeName { get; }
// private AuthenticationScheme _scheme;
protected IHttpClientFactory HttpClientFactory { get; }
protected HttpClient HttpClient { get; }
public OAuthAuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder,
IHttpClientFactory httpClientFactory) :
base(options, logger, encoder)
{
HttpClientFactory = httpClientFactory;
HttpClient = httpClientFactory.CreateClient();
}
/// <summary>
/// Claims 转换为 票据
/// </summary>
/// <param name="claims"></param>
/// <returns></returns>
private AuthenticationTicket ConvertTicket(List<Claim> claims)
{
var claimsIdentity = new ClaimsIdentity(claims.ToArray(), AuthenticationSchemeName);
var principal = new ClaimsPrincipal(claimsIdentity);
return new AuthenticationTicket(principal, AuthenticationSchemeName);
}
protected async Task<THttpModel> SendHttpRequestAsync<THttpModel>(string url,
IEnumerable<KeyValuePair<string, string?>> query,
HttpMethod? httpMethod = null)
{
httpMethod ??= HttpMethod.Get;
var queryUrl = QueryHelpers.AddQueryString(url, query);
HttpResponseMessage response = null;
if (httpMethod == HttpMethod.Get)
{
response = await HttpClient.GetAsync(queryUrl);
}
else if (httpMethod == HttpMethod.Post)
{
response = await HttpClient.PostAsync(queryUrl, null);
}
if (response == null)
{
throw new NotSupportedException($"不支持的请求方式: {httpMethod}");
}
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new Exception($"授权服务器请求错误,请求地址[{queryUrl}] 错误信息[{content}]");
}
VerifyErrResponse(content);
var model = JsonConvert.DeserializeObject<THttpModel>(content);
return model!;
}
protected virtual void VerifyErrResponse(string content)
{
AuthenticationErrCodeModel.VerifyErrResponse(content);
}
/// <summary>
/// 获取登录票据
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
protected abstract Task<List<Claim>> GetAuthTicketAsync(string code);
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Context.Request.Query.ContainsKey("code"))
{
return AuthenticateResult.Fail("回调中未包含code参数");
}
var code = Context.Request.Query["code"].ToString();
List<Claim> authTicket;
try
{
authTicket = await GetAuthTicketAsync(code);
}
catch (Exception ex)
{
return AuthenticateResult.Fail(ex.Message ?? "未知错误");
}
// 获取登录票据成功
return AuthenticateResult.Success(ConvertTicket(authTicket));
}
}