|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
using System;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
@ -17,8 +17,6 @@ namespace AuthStudy.Authentication.Basic
|
|
|
|
|
{
|
|
|
|
|
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
|
|
|
|
|
{
|
|
|
|
|
private const string _Scheme = "BasicScheme";
|
|
|
|
|
|
|
|
|
|
private readonly UTF8Encoding _utf8ValidatingEncoding = new UTF8Encoding(false, true);
|
|
|
|
|
|
|
|
|
|
public BasicAuthenticationHandler
|
|
|
|
@ -42,69 +40,21 @@ namespace AuthStudy.Authentication.Basic
|
|
|
|
|
|
|
|
|
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
|
|
|
|
{
|
|
|
|
|
string? authorizationHeader = Request.Headers["Authorization"];
|
|
|
|
|
if (string.IsNullOrEmpty(authorizationHeader))
|
|
|
|
|
{
|
|
|
|
|
return AuthenticateResult.NoResult();
|
|
|
|
|
}
|
|
|
|
|
string requestBasicText = GetRequestBasic();
|
|
|
|
|
|
|
|
|
|
// Exact match on purpose, rather than using string compare
|
|
|
|
|
// asp.net request parsing will always trim the header and remove trailing spaces
|
|
|
|
|
if (_Scheme == authorizationHeader)
|
|
|
|
|
//认证信息
|
|
|
|
|
var basicInfo = ParseBasicInfo(requestBasicText);
|
|
|
|
|
if (!basicInfo.ParseResult)
|
|
|
|
|
{
|
|
|
|
|
const string noCredentialsMessage = "Authorization scheme was Basic but the header had no credentials.";
|
|
|
|
|
Logger.LogInformation(noCredentialsMessage);
|
|
|
|
|
return AuthenticateResult.Fail(noCredentialsMessage);
|
|
|
|
|
return AuthenticateResult.Fail(basicInfo.Message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!authorizationHeader.StartsWith(_Scheme + ' ', StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
return AuthenticateResult.NoResult();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string encodedCredentials = authorizationHeader.Substring(_Scheme.Length).Trim();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string decodedCredentials = string.Empty;
|
|
|
|
|
byte[] base64DecodedCredentials;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
base64DecodedCredentials = Convert.FromBase64String(encodedCredentials);
|
|
|
|
|
}
|
|
|
|
|
catch (FormatException)
|
|
|
|
|
{
|
|
|
|
|
const string failedToDecodeCredentials = "Cannot convert credentials from Base64.";
|
|
|
|
|
Logger.LogInformation(failedToDecodeCredentials);
|
|
|
|
|
return AuthenticateResult.Fail(failedToDecodeCredentials);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
decodedCredentials = _utf8ValidatingEncoding.GetString(base64DecodedCredentials);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
const string failedToDecodeCredentials = "Cannot build credentials from decoded base64 value, exception {ex.Message} encountered.";
|
|
|
|
|
Logger.LogInformation(failedToDecodeCredentials, ex.Message);
|
|
|
|
|
return AuthenticateResult.Fail(ex.Message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var delimiterIndex = decodedCredentials.IndexOf(":", StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
if (delimiterIndex == -1)
|
|
|
|
|
{
|
|
|
|
|
const string missingDelimiterMessage = "Invalid credentials, missing delimiter.";
|
|
|
|
|
Logger.LogInformation(missingDelimiterMessage);
|
|
|
|
|
return AuthenticateResult.Fail(missingDelimiterMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var username = decodedCredentials.Substring(0, delimiterIndex);
|
|
|
|
|
var password = decodedCredentials.Substring(delimiterIndex + 1);
|
|
|
|
|
|
|
|
|
|
var validateCredentialsContext = new ValidateCredentialsContext(Context, Scheme, Options)
|
|
|
|
|
{
|
|
|
|
|
Username = username,
|
|
|
|
|
Password = password
|
|
|
|
|
Username = basicInfo.UserName,
|
|
|
|
|
Password = basicInfo.Password
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (Events != null)
|
|
|
|
@ -113,10 +63,9 @@ namespace AuthStudy.Authentication.Basic
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (validateCredentialsContext.Result != null &&
|
|
|
|
|
validateCredentialsContext.Result.Succeeded)
|
|
|
|
|
if (validateCredentialsContext.Result != null && validateCredentialsContext.Result.Succeeded)
|
|
|
|
|
{
|
|
|
|
|
var ticket = new AuthenticationTicket(validateCredentialsContext.Principal, Scheme.Name);
|
|
|
|
|
var ticket = new AuthenticationTicket(validateCredentialsContext.Principal!, Scheme.Name);
|
|
|
|
|
return AuthenticateResult.Success(ticket);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -151,9 +100,16 @@ namespace AuthStudy.Authentication.Basic
|
|
|
|
|
|
|
|
|
|
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
|
|
|
|
|
{
|
|
|
|
|
//如果响应已经开始,则忽略
|
|
|
|
|
//比如同时使用其它认证,失败后已经被设置了
|
|
|
|
|
if (Response.HasStarted)
|
|
|
|
|
{
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Request.IsHttps && !Options.AllowInsecureProtocol)
|
|
|
|
|
{
|
|
|
|
|
const string insecureProtocolMessage = "Request is HTTP, Basic Authentication will not respond.";
|
|
|
|
|
const string insecureProtocolMessage = "请求为HTTP,基本身份验证将不响应。";
|
|
|
|
|
Logger.LogInformation(insecureProtocolMessage);
|
|
|
|
|
Response.StatusCode = StatusCodes.Status421MisdirectedRequest;
|
|
|
|
|
}
|
|
|
|
@ -162,12 +118,106 @@ namespace AuthStudy.Authentication.Basic
|
|
|
|
|
Response.StatusCode = 401;
|
|
|
|
|
if (!Options.SuppressWWWAuthenticateHeader)
|
|
|
|
|
{
|
|
|
|
|
var headerValue = _Scheme + $" realm=\"{Options.Realm}\"";
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|