HTTP认证之基本认证——Basic(二)

在HTTP认证之基本认证——Basic(一)中介绍了Basic认证的工作原理和流程,接下来就赶紧通过代码来实践一下,以下教程基于ASP.NET Core WebApi框架。如有兴趣,可查看源码

一、准备工作

在开始之前,先把最基本的用户名密码校验逻辑准备好,只有一个认证方法:

public class UserService { public static User Authenticate(string userName, string password) { //用户名、密码不为空且相等时认证成功 if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password) && userName == password) { return new User() { UserName = userName, Password = password }; } return null; } } public class User { public string UserName { get; set; } public string Password { get; set; } } 二、编码

1.首先,先确定使用的认证方案为Basic,并提供默认的的Realm,

public const string AuthenticationScheme = "Basic"; public const string AuthenticationRealm = "Test Realm";

2.然后,解析HTTP Request获取到Authorization标头

private string GetCredentials(HttpRequest request) { string credentials = null; string authorization = request.Headers[HeaderNames.Authorization]; //请求中存在 Authorization 标头且认证方式为 Basic if (authorization?.StartsWith(AuthenticationScheme, StringComparison.OrdinalIgnoreCase) == true) { credentials = authorization.Substring(AuthenticationScheme.Length).Trim(); } return credentials; }

3.接着通过Base64逆向解码,得到要认证的用户名和密码。如果认证失败,则返回401 Unauthorized(不推荐返回403 Forbidden,因为这会导致用户在不刷新页面的情况下无法重新尝试认证);如果认证成功,继续处理请求。

public class AuthorizationFilterAttribute : Attribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { //请求允许匿名访问 if (context.Filters.Any(item => item is IAllowAnonymousFilter)) return; var credentials = GetCredentials(context.HttpContext.Request); //已获取到凭证 if(credentials != null) { try { //Base64逆向解码得到用户名和密码 credentials = Encoding.UTF8.GetString(Convert.FromBase64String(credentials)); var data = credentials.Split(':'); if (data.Length == 2) { var userName = data[0]; var password = data[1]; var user = UserService.Authenticate(userName, password); //认证成功 if (user != null) return; } } catch { } } //认证失败返回401 context.Result = new UnauthorizedResult(); //添加质询 AddChallenge(context.HttpContext.Response); } private void AddChallenge(HttpResponse response) => response.Headers.Append(HeaderNames.WWWAuthenticate, $"{ AuthenticationScheme } Realm={ AuthenticationRealm }"); }

4.最后,在需要认证的Action上加上过滤器[AuthorizationFilter],大功告成!自己测试一下吧

三、封装为中间件

相比最大的突破大概就是插件配置化了——通过将各个功能封装成中间件,应用AOP的设计思想配置到应用程序中。以下封装采用Jwt Bearer封装规范。

首先封装常量

public static class BasicDefaults { public const string AuthenticationScheme = "Basic"; }

2.然后封装Basic认证的Options,包括Realm和事件,继承自Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions。在事件内部,我们定义了认证行为和质询行为,分别用来校验认证是否通过和在HTTP Response中添加质询信息。我们将认证逻辑封装成一个委托,与认证行为独立开来,方便用户使用委托自定义认证规则。

public class BasicOptions : AuthenticationSchemeOptions { public string Realm { get; set; } public new BasicEvents Events { get => (BasicEvents)base.Events; set => base.Events = value; } } public class BasicEvents { public Func<ValidateCredentialsContext, Task> OnValidateCredentials { get; set; } = context => Task.CompletedTask; public Func<BasicChallengeContext, Task> OnChallenge { get; set; } = context => Task.CompletedTask; public virtual Task ValidateCredentials(ValidateCredentialsContext context) => OnValidateCredentials(context); public virtual Task Challenge(BasicChallengeContext context) => OnChallenge(context); } /// <summary> /// 封装认证参数信息上下文 /// </summary> public class ValidateCredentialsContext : ResultContext<BasicAuthenticationOptions> { public ValidateCredentialsContext(HttpContext context, AuthenticationScheme scheme, BasicAuthenticationOptions options) : base(context, scheme, options) { } public string UserName { get; set; } public string Password { get; set; } } public class BasicChallengeContext : PropertiesContext<BasicOptions> { public BasicChallengeContext( HttpContext context, AuthenticationScheme scheme, BasicOptions options, AuthenticationProperties properties) : base(context, scheme, options, properties) { } /// <summary> /// 在认证期间出现的异常 /// </summary> public Exception AuthenticateFailure { get; set; } /// <summary> /// 指定是否已被处理,如果已处理,则跳过默认认证逻辑 /// </summary> public bool Handled { get; private set; } /// <summary> /// 跳过默认认证逻辑 /// </summary> public void HandleResponse() => Handled = true; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpysyp.html