diff --git a/AuthStudy.Authentication.Basic/BasicAuthenticationHandler.cs b/AuthStudy.Authentication.Basic/BasicAuthenticationHandler.cs index 5d7b982..a3c398b 100644 --- a/AuthStudy.Authentication.Basic/BasicAuthenticationHandler.cs +++ b/AuthStudy.Authentication.Basic/BasicAuthenticationHandler.cs @@ -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 { - 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 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; + } } } diff --git a/AuthStudy.Authentication.Basic/BasicAuthenticationOptions.cs b/AuthStudy.Authentication.Basic/BasicAuthenticationOptions.cs index 8bb3cc4..87c0c4e 100644 --- a/AuthStudy.Authentication.Basic/BasicAuthenticationOptions.cs +++ b/AuthStudy.Authentication.Basic/BasicAuthenticationOptions.cs @@ -61,7 +61,7 @@ namespace AuthStudy.Authentication.Basic public bool AllowInsecureProtocol { get; set; - } + } = true; /// /// 事件 diff --git a/AuthStudy.WebApp/Controllers/AccountsController.cs b/AuthStudy.WebApp/Controllers/AccountsController.cs index 554314f..387b75c 100644 --- a/AuthStudy.WebApp/Controllers/AccountsController.cs +++ b/AuthStudy.WebApp/Controllers/AccountsController.cs @@ -1,6 +1,7 @@ using AuthStudy.Authentication.Browser; using AuthStudy.WebApp.VModels; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -16,14 +17,18 @@ namespace AuthStudy.WebApp.Controllers } + //多特性是and特性内逗号分隔是or + //[Authorize] //[Authorize(AuthenticationSchemes = AuthenticationSchemeList.BaseBrowserScheme)] //[Authorize(AuthenticationSchemes = AuthenticationSchemeList.BrowserScheme)] //[Authorize(AuthenticationSchemes = AuthenticationSchemeList.BasicScheme)] - [Authorize(AuthenticationSchemes = $"{AuthenticationSchemeList.BaseBrowserScheme},{AuthenticationSchemeList.BrowserScheme},{AuthenticationSchemeList.BasicScheme}")] - //[Authorize] + //[Authorize(AuthenticationSchemes = $"{AuthenticationSchemeList.BrowserScheme},{AuthenticationSchemeList.BasicScheme}")] + //[Authorize(AuthenticationSchemes = $"{AuthenticationSchemeList.BaseBrowserScheme},{AuthenticationSchemeList.BrowserScheme},{AuthenticationSchemeList.BasicScheme}")] [HttpGet] - public IActionResult GetAll() + public async Task GetAll() { + var dd = await HttpContext.AuthenticateAsync(); + //输出认证信息 foreach (var claim in User.Claims) { diff --git a/AuthStudy.WebApp/Program.cs b/AuthStudy.WebApp/Program.cs index 4f3e4d4..2810b30 100644 --- a/AuthStudy.WebApp/Program.cs +++ b/AuthStudy.WebApp/Program.cs @@ -1,5 +1,8 @@ +using System.Security.Claims; + using AuthStudy.Authentication.Basic; +using AuthStudy.Authentication.Basic.Events; using AuthStudy.Authentication.Browser; namespace AuthStudy.WebApp @@ -34,7 +37,30 @@ namespace AuthStudy.WebApp option.AllowBrowsers = new List() { "Edge", "Chrome", "Firefox" }; }) //基本认证 - .AddBasic(AuthenticationSchemeList.BasicScheme); + .AddBasic(options => + { + options.Realm = "Basic Authentication"; + options.Events = new BasicAuthenticationEvents + { + OnValidateCredentials = context => + { + if (context.Username == context.Password) + { + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer), + new Claim(ClaimTypes.Name, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer) + }; + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); + context.Success(); + } + + return Task.CompletedTask; + } + }; + + }); //默认基类实现注册 @@ -52,6 +78,11 @@ namespace AuthStudy.WebApp app.MapControllers(); app.Run(); + + void Test() + { + + } } } } \ No newline at end of file