|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|