feat: basic和新项目Digest

main
bicijinlian 1 year ago
parent 1ee04d29a2
commit 53eab0dabf

@ -6,7 +6,8 @@ using System.Threading.Tasks;
namespace AuthStudy.Authentication.Basic
{
internal class BasicAuthenticationDefaults
public class BasicAuthenticationDefaults
{
public const string AuthenticationScheme = "BasicScheme";
}
}

@ -4,9 +4,35 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
namespace AuthStudy.Authentication.Basic
{
internal class BasicAuthenticationExtensions
public static class BasicAuthenticationExtensions
{
public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder)
{
return builder.AddBasic(BasicAuthenticationDefaults.AuthenticationScheme);
}
public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme)
{
return builder.AddBasic(authenticationScheme, configureOptions: null);
}
public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action<BasicAuthenticationOptions> configureOptions)
{
return builder.AddBasic(BasicAuthenticationDefaults.AuthenticationScheme, configureOptions);
}
public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action<BasicAuthenticationOptions>? configureOptions)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
return builder.AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>(authenticationScheme, configureOptions);
}
}
}

@ -2,11 +2,172 @@
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
{
internal class BasicAuthenticationHandler
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
private const string _Scheme = "BasicScheme";
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? authorizationHeader = Request.Headers["Authorization"];
if (string.IsNullOrEmpty(authorizationHeader))
{
return AuthenticateResult.NoResult();
}
// 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)
{
const string noCredentialsMessage = "Authorization scheme was Basic but the header had no credentials.";
Logger.LogInformation(noCredentialsMessage);
return AuthenticateResult.Fail(noCredentialsMessage);
}
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
};
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 (!Request.IsHttps && !Options.AllowInsecureProtocol)
{
const string insecureProtocolMessage = "Request is HTTP, Basic Authentication will not respond.";
Logger.LogInformation(insecureProtocolMessage);
Response.StatusCode = StatusCodes.Status421MisdirectedRequest;
}
else
{
Response.StatusCode = 401;
if (!Options.SuppressWWWAuthenticateHeader)
{
var headerValue = _Scheme + $" realm=\"{Options.Realm}\"";
Response.Headers.Append(HeaderNames.WWWAuthenticate, headerValue);
}
}
return Task.CompletedTask;
}
}
}

@ -1,13 +1,94 @@

using AuthStudy.Authentication.Basic.Events;
using Microsoft.AspNetCore.Authentication;
namespace AuthStudy.Authentication.Basic
{
public class BasicAuthenticationOptions : AuthenticationSchemeOptions
{
private string _realm = string.Empty;
/// <summary>
/// 创建用默认值初始化的选项的实例
/// </summary>
public BasicAuthenticationOptions()
{
}
/// <summary>
/// 获取或设置在WWW-Authenticate标头中发送的 Realm
/// </summary>
/// <remarks>
/// Realm值区分大小写与正在访问的服务器的规范根URL相结合定义了保护空间。
/// 这些领域允许将服务器上受保护的资源划分为一组保护空间,每个空间都有自己的身份验证方案和/或授权数据库。
/// </remarks>
public string Realm
{
get
{
return _realm;
}
set
{
if (!string.IsNullOrEmpty(value) && !IsAscii(value))
{
throw new ArgumentException("Realm must be US ASCII");
}
_realm = value;
}
}
/// <summary>
/// 获取或设置一个标志该标志指示是否将在未经授权的响应上取消WWW-Authenticate标头
/// </summary>
/// <remarks>
/// 身份验证方案控制浏览器UI并允许浏览器以正确的方式进行身份验证弹出一个允许输入用户名和密码的UI
/// 有些用户可能希望对JavaScript XMLHttpRequest请求抑制这种行为
/// 将此标志设置为True将抑制WWW-Authenticate标头从而抑制浏览器登录提示只需发送401状态代码您就必须对自己做出反应。
/// </remarks>
public bool SuppressWWWAuthenticateHeader
{
get; set;
}
/// <summary>
/// 获取或设置一个标志该标志指示处理程序是否会提示对HTTP请求进行身份验证
/// </summary>
public bool AllowInsecureProtocol
{
get; set;
}
/// <summary>
/// 事件
/// </summary>
public new BasicAuthenticationEvents? Events
{
get { return (BasicAuthenticationEvents?)base.Events; }
set { base.Events = value; }
}
/// <summary>
/// 判断给定的字符串是否全部是ASCII字符
/// </summary>
private static bool IsAscii(string input)
{
foreach (char c in input)
{
if (c < 32 || c >= 127)
{
return false;
}
}
return true;
}
}
}

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AuthStudy.Authentication.Basic.Events
{
public class BasicAuthenticationEvents
{
public Func<BasicAuthenticationFailedContext, Task> OnAuthenticationFailed { get; set; } = context => Task.CompletedTask;
public Func<ValidateCredentialsContext, Task> OnValidateCredentials { get; set; } = context => Task.CompletedTask;
public virtual Task AuthenticationFailed(BasicAuthenticationFailedContext context) => OnAuthenticationFailed(context);
public virtual Task ValidateCredentials(ValidateCredentialsContext context) => OnValidateCredentials(context);
}
}

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
namespace AuthStudy.Authentication.Basic.Events
{
public class BasicAuthenticationFailedContext : ResultContext<BasicAuthenticationOptions>
{
public BasicAuthenticationFailedContext(HttpContext context, AuthenticationScheme scheme, BasicAuthenticationOptions options)
: base(context, scheme, options)
{
}
public Exception? Exception { get; set; }
}
}

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
namespace AuthStudy.Authentication.Basic.Events
{
public class ValidateCredentialsContext : ResultContext<BasicAuthenticationOptions>
{
public ValidateCredentialsContext(HttpContext context, AuthenticationScheme scheme, BasicAuthenticationOptions options)
: base(context, scheme, options)
{
}
/// <summary>
/// 用户名
/// </summary>
public string? Username { get; set; }
/// <summary>
/// 密码
/// </summary>
public string? Password { get; set; }
}
}

@ -85,8 +85,8 @@ namespace AuthStudy.Authentication.Browser
//声明(身份项)
var browser = new Claim("Browser", clientInfo.UA.ToString()); //浏览器
var os = new Claim("OS", clientInfo.OS.ToString()); //操作系统
var device = new Claim("Device", clientInfo.Device.ToString()); //设备 //设备
var os = new Claim("OS", clientInfo.OS.ToString()); //操作系统
var device = new Claim("Device", clientInfo.Device.ToString()); //设备 //设备
//声明集合
var Claims = new List<Claim>

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App"></FrameworkReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AuthStudy.Authentication.Shared\AuthStudy.Authentication.Shared.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,7 @@
namespace AuthStudy.Authentication.Digest
{
public class Class1
{
}
}

@ -16,5 +16,10 @@
/// 基类实现方式
/// </summary>
public const string BrowserScheme = "BrowserScheme";
/// <summary>
/// 基本身份认证
/// </summary>
public const string BasicScheme = "BasicScheme";
}
}

@ -14,6 +14,7 @@
<ItemGroup>
<ProjectReference Include="..\AuthStudy.Authentication.Basic\AuthStudy.Authentication.Basic.csproj" />
<ProjectReference Include="..\AuthStudy.Authentication.Browser\AuthStudy.Authentication.Browser.csproj" />
<ProjectReference Include="..\AuthStudy.Authentication.Digest\AuthStudy.Authentication.Digest.csproj" />
<ProjectReference Include="..\AuthStudy.Authentication.Shared\AuthStudy.Authentication.Shared.csproj" />
<ProjectReference Include="..\AuthStudy.Authentication.SqlServer\AuthStudy.Authentication.SqlServer.csproj" />
<ProjectReference Include="..\AuthStudy.Authentication.UrlQuery\AuthStudy.Authentication.UrlQuery.csproj" />

@ -16,8 +16,11 @@ namespace AuthStudy.WebApp.Controllers
}
//[Authorize(AuthenticationSchemes = BrowserAuthenticationDefault.SchemeName)]
[Authorize(AuthenticationSchemes = "BrowserAuthenticationHandlerByBase")]
//[Authorize(AuthenticationSchemes = AuthenticationSchemeList.BaseBrowserScheme)]
//[Authorize(AuthenticationSchemes = AuthenticationSchemeList.BrowserScheme)]
//[Authorize(AuthenticationSchemes = AuthenticationSchemeList.BasicScheme)]
[Authorize(AuthenticationSchemes = $"{AuthenticationSchemeList.BaseBrowserScheme},{AuthenticationSchemeList.BrowserScheme},{AuthenticationSchemeList.BasicScheme}")]
//[Authorize]
[HttpGet]
public IActionResult GetAll()
{

@ -1,4 +1,5 @@
using AuthStudy.Authentication.Basic;
using AuthStudy.Authentication.Browser;
namespace AuthStudy.WebApp
@ -27,10 +28,13 @@ namespace AuthStudy.WebApp
);
builder.Services
.AddAuthentication(AuthenticationSchemeList.BaseBrowserScheme)
//浏览器认证
.AddScheme<BrowserAuthenticationOptions, BrowserAuthenticationHandler>(AuthenticationSchemeList.BaseBrowserScheme, option =>
{
option.AllowBrowsers = new List<string>() { "Edge", "Chrome", "Firefox" };
});
})
//基本认证
.AddBasic(AuthenticationSchemeList.BasicScheme);
//默认基类实现注册

@ -24,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{947159C1-4
Docs\说明.md = Docs\说明.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthStudy.Authentication.Digest", "AuthStudy.Authentication.Digest\AuthStudy.Authentication.Digest.csproj", "{40540D06-49E7-4F39-AE72-66F8A074D86F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -54,6 +56,10 @@ Global
{946FECCE-6382-4138-B840-4189B4F7108A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{946FECCE-6382-4138-B840-4189B4F7108A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{946FECCE-6382-4138-B840-4189B4F7108A}.Release|Any CPU.Build.0 = Release|Any CPU
{40540D06-49E7-4F39-AE72-66F8A074D86F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40540D06-49E7-4F39-AE72-66F8A074D86F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40540D06-49E7-4F39-AE72-66F8A074D86F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40540D06-49E7-4F39-AE72-66F8A074D86F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -64,6 +70,7 @@ Global
{F320DAFE-F490-4F2B-B2C8-F32E71903304} = {0F6CCE40-BD73-4103-99A0-8FE31B4CC7E6}
{C9F1FEDE-FEBC-4507-B70B-7A4DC1AD88EB} = {0F6CCE40-BD73-4103-99A0-8FE31B4CC7E6}
{946FECCE-6382-4138-B840-4189B4F7108A} = {0F6CCE40-BD73-4103-99A0-8FE31B4CC7E6}
{40540D06-49E7-4F39-AE72-66F8A074D86F} = {0F6CCE40-BD73-4103-99A0-8FE31B4CC7E6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {93CE07D0-855A-4D3E-B5FF-D9E9D9142536}

Loading…
Cancel
Save