+-
NetCore配置BasicAuth用户认证功能

1、Http BasicAuth 概述

Http BasicAuth 即 Http 基本认证,其最简单的实质是,服务器允许客户端携带账号密码/加密认证请求头来访问受保护资源,例如:

curl http://username:password@localhost/api/users 实现访问受保护资源 Http请求头中,加入 Authorization, 值为 Basic base64str 实现访问受保护资源
base64str 实际为 username:password 字符串经过base64算法编码后的字符串

基本的交互流程如下图所示

image-20200906080253594

2、项目搭建

根据以下步骤,创建一个基本的NetCore项目(基于NetCore3.1版本)

image-20200906075058998

image-20200906075249985

根据上面三个图所示,我们即可创建好一个最基本的NetCore项目

3. BasicAuth配置

3.1 自定义AuthenticationHandler

通过自定义AuthenticationHandler实现BasicAuth认证,实现代码如下:

using ApiBasicAuth.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace ApiBasicAuth.Security
{
    public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {

        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock)
            : base(options, logger, encoder, clock)
        {
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
            {
                Response.Headers.Add("WWW-Authenticate", @"Basic realm='Secure Area'");
                return AuthenticateResult.Fail("Missing Authorization Header");
            }
            User user = null;
            try
            {
                var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                var username = credentials[0];
                var password = credentials[1];
                if (username.Equals("admin") && password.Equals("password"))
                {
                    user = new User { Id=1, Username = "admin", Birthday = DateTime.Now };
                }
            }
            catch
            {
                // Base64字符串解码失败
                return AuthenticateResult.Fail("Invalid Authorization Header");
            }

            if (user == null)
                return AuthenticateResult.Fail("Invalid Username or Password");

            var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            };
            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }
}

其中有几点值得比较注意:

Response.Headers.Add("WWW-Authenticate", @"Basic realm='Secure Area'"); 在认证失败时,返回这个请求头,则是告诉浏览器,要求弹出认证信息输入窗口(浏览器自身功能),若没返回此请求头,则浏览器不会弹出自带账号/密码输入窗口,直接显示401 if (username.Equals("admin") && password.Equals("password")) 在示例代码中,直接固定只支持admin账号,在实际中,因该是根据诸如 UserService等服务层,来判断用户账号信息
 var claims = new[] {
     new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
     new Claim(ClaimTypes.Name, user.Username),
 };

在示例中,默认只是给当前用户设置了两个基本属性,在实际中,我们可以根据用户的角色/权限等信息,注入对应的Claim,如:

var roles = new List<string>();
foreach (var item in roles)
{
    claims.Append(new Claim(ClaimTypes.Role, item));
};

3.2 修改NetCore认证配置

public void ConfigureServices(IServiceCollection services)中,增加如下配置

 services.AddAuthentication("BasicAuthentication")
 .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
            

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)中,增加如下配置:

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

至此,项目的认证服务配置,搭建完成

4、认证功能验证

通过上面的章节,我们已经配置好认证服务,下面则是开始构建Controller,来校验认证是否生效。如下图,我们便携一个最基本的Controller

namespace ApiBasicAuth.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class UserController : ControllerBase
    {

        [HttpGet]
        [AllowAnonymous]
        public IActionResult NoSecurity() {

            return Ok("NoSecurity");
        }


        [HttpGet]
        [Authorize]
        public IActionResult Security()
        {
            return Ok("Security");
        }
    }
}

其中

/api/User/Security 需要认证后才能访问 /api/User/NoSecuirty不需要认证就能访问
[AllowAnonymous] 表示接口任何人都能访问 [Authorize] 标识接口需要登陆认证后,才能进行访问

我们开始进行校验:

访问 /api/User/Security 不携带认证信息

image-20200906144148642

请求返回401,表明未认证,则代表我们配置的认证服务已生效。

访问 /api/User/Security携带认证信息

image-20200906144324772

可以看到,返回Security,表明已经认证通过

访问 /api/User/NoSecurity不携带认证信息

image-20200906144553003

可以看到,对于无需认证的接口,不携带认证信息也能获取内容。

5、结语

至此,BasicAuth 认证服务就配置验证完成,其实我们可以通过这个例子,可以衍生推导出如何实现JWT认证服务,基本原理一致,只是需要稍微修改BasicAuthenticationHandler配置即可,后续这边也会编写NetCore实现自定义JWT认证服务相关文章。

欢迎关注公众号,获取最新文章信息

image