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#

1 year ago
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()
{
1 year ago
string requestBasicText = GetRequestBasic();
1 year ago
//认证信息
var basicInfo = ParseBasicInfo(requestBasicText);
if (!basicInfo.ParseResult)
{
1 year ago
return AuthenticateResult.Fail(basicInfo.Message);
}
try
{
var validateCredentialsContext = new ValidateCredentialsContext(Context, Scheme, Options)
{
1 year ago
Username = basicInfo.UserName,
Password = basicInfo.Password
};
if (Events != null)
{
await Events.ValidateCredentials(validateCredentialsContext);
}
1 year ago
if (validateCredentialsContext.Result != null && validateCredentialsContext.Result.Succeeded)
{
1 year ago
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)
{
1 year ago
//如果响应已经开始,则忽略
//比如同时使用其它认证,失败后已经被设置了
if (Response.HasStarted)
{
return Task.CompletedTask;
}
if (!Request.IsHttps && !Options.AllowInsecureProtocol)
{
1 year ago
const string insecureProtocolMessage = "请求为HTTP基本身份验证将不响应。";
Logger.LogInformation(insecureProtocolMessage);
Response.StatusCode = StatusCodes.Status421MisdirectedRequest;
}
else
{
Response.StatusCode = 401;
if (!Options.SuppressWWWAuthenticateHeader)
{
1 year ago
var headerValue = BasicAuthenticationDefaults.AuthenticationScheme + $" realm=\"{Options.Realm}\"";
Response.Headers.Append(HeaderNames.WWWAuthenticate, headerValue);
}
}
return Task.CompletedTask;
}
1 year ago
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;
}
}
}