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.

224 lines
7.4 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using AuthStudy.Authentication.Basic.Events;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace AuthStudy.Authentication.Basic
{
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
private readonly UTF8Encoding _utf8ValidatingEncoding = new UTF8Encoding(false, true);
public BasicAuthenticationHandler
(
IOptionsMonitor<BasicAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock
)
: base(options, logger, encoder, clock)
{
}
protected new BasicAuthenticationEvents? Events
{
get { return (BasicAuthenticationEvents?)base.Events; }
set { base.Events = value; }
}
protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new BasicAuthenticationEvents());
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string requestBasicText = GetRequestBasic();
//认证信息
var basicInfo = ParseBasicInfo(requestBasicText);
if (!basicInfo.ParseResult)
{
return AuthenticateResult.Fail(basicInfo.Message);
}
try
{
var validateCredentialsContext = new ValidateCredentialsContext(Context, Scheme, Options)
{
Username = basicInfo.UserName,
Password = basicInfo.Password
};
if (Events != null)
{
await Events.ValidateCredentials(validateCredentialsContext);
}
if (validateCredentialsContext.Result != null && validateCredentialsContext.Result.Succeeded)
{
var ticket = new AuthenticationTicket(validateCredentialsContext.Principal!, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
if (validateCredentialsContext.Result != null &&
validateCredentialsContext.Result.Failure != null)
{
return AuthenticateResult.Fail(validateCredentialsContext.Result.Failure);
}
return AuthenticateResult.NoResult();
}
catch (Exception ex)
{
var authenticationFailedContext = new BasicAuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
if (Events != null)
{
await Events.AuthenticationFailed(authenticationFailedContext).ConfigureAwait(true);
}
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
//如果响应已经开始,则忽略
//比如同时使用其它认证,失败后已经被设置了
if (Response.HasStarted)
{
return Task.CompletedTask;
}
if (!Request.IsHttps && !Options.AllowInsecureProtocol)
{
const string insecureProtocolMessage = "请求为HTTP基本身份验证将不响应。";
Logger.LogInformation(insecureProtocolMessage);
Response.StatusCode = StatusCodes.Status421MisdirectedRequest;
}
else
{
Response.StatusCode = 401;
if (!Options.SuppressWWWAuthenticateHeader)
{
var headerValue = BasicAuthenticationDefaults.AuthenticationScheme + $" realm=\"{Options.Realm}\"";
Response.Headers.Append(HeaderNames.WWWAuthenticate, headerValue);
}
}
return Task.CompletedTask;
}
private string GetRequestBasic()
{
//请求头接收
string basicText = Request.Headers["Authorization"].ToString();
if (string.IsNullOrWhiteSpace(basicText))
{
basicText = Request.Headers["Authorization"].ToString();
}
//URL参数
if (string.IsNullOrWhiteSpace(basicText))
{
basicText = $"{Request.Query["Basic"]}";
}
//URL参数
if (string.IsNullOrWhiteSpace(basicText))
{
basicText = $"{Request.Query["User"]}:{Request.Query["Password"]}";
basicText = basicText.TrimStart(':');
}
return basicText;
}
private (bool ParseResult, string Message, string UserName, string Password) ParseBasicInfo(string requestBasic)
{
(bool ParseResult, string Message, string UserName, string Password) result = (true, "格式错误", "", "");
if (string.IsNullOrWhiteSpace(requestBasic))
{
result.Message = "无认证信息";
return result;
}
requestBasic = requestBasic.Trim();
if (requestBasic.Equals("Basic", StringComparison.OrdinalIgnoreCase))
{
result.Message = "授权方案是Basic但标头没有内容!";
return result;
}
var basicList = requestBasic.Split(':');
if (basicList.Length == 2)
{
result.ParseResult = true;
result.Message = "认证信息正确";
result.UserName = basicList[0];
result.Password = basicList[1];
return result;
}
requestBasic = requestBasic.Replace("Basic ", "", StringComparison.OrdinalIgnoreCase);
string decodedCredentials = string.Empty;
byte[] base64DecodedCredentials;
try
{
base64DecodedCredentials = Convert.FromBase64String(requestBasic);
}
catch (FormatException)
{
result.ParseResult = false;
result.Message = "凭据不是Base64格式";
return result;
}
try
{
decodedCredentials = _utf8ValidatingEncoding.GetString(base64DecodedCredentials);
}
catch (Exception ex)
{
result.ParseResult = false;
result.Message = $"认证信息生成凭据时异常:{ex.Message}";
return result;
}
var list = decodedCredentials.Split(':');
if (list.Length != 2)
{
result.ParseResult = false;
result.Message = $"凭据无效,缺少分隔符!";
return result;
}
result.ParseResult = true;
result.Message = "认证信息正确";
result.UserName = list[0];
result.Password = list[1];
return result;
}
}
}