'UseAuthentication()' 到底是做什么用的?

What exactly is 'UseAuthentication()' for?

我有一个关于 ASP.NET 核心 2 中身份验证的问题:调用 app.UseAuthentication() 到底是为了什么?

这是我可以实施自定义身份验证逻辑的基本先决条件吗?我已经看过 UseAuthentication and also of the actual middleware AuthenticationMiddleware 的实现,但老实说,我不明白它实际在做什么以及为什么有必要这样做。

换句话说:

我需要调用 UseAuthentication()

或者这是一个很好的选择,我仍然可以进行自定义身份验证?

如果我不调用 UseAuthentication() 就没事,我仍然会对 AuthenticationMiddleware 实际在做什么感兴趣。因此,如果您知道这一点,如果您也能为我解释一下,我将不胜感激。

你确实需要调用它。

UseAuthentication() 记录为:

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.authappbuilderextensions.useauthentication?view=aspnetcore-2.0

Adds the AuthenticationMiddleware to the specified IApplicationBuilder, which enables authentication capabilities.

它基本上是这样做的:

IApplicationBuilder AddAuthentication(this IApplicationBuilder app) {

    return app.UseMiddleware<AuthenticationMiddleware>();
}

...所以它只是为您节省了一些输入和可能的一些额外的 using 导入。

这会将一个 AuthenticationMiddleware 实例添加到进程的 request-handling 管道中,并且此特定对象会添加用于身份验证的管道。

如果您编写自定义中间件(就像您在示例中所做的那样),则不需要调用 AddAuthentication,因为身份验证中间件不会知道您自己的中间件。

也就是说,您可能不想创建自己的中间件:您可能想创建一个新的身份验证处理程序,它可以很好地与 ASP.NET 身份验证框架配合使用(以便您使用 [Authorize]控制器上的属性)。

要创建自定义身份验证,您必须创建一个继承自 AuthenticationHandler 的专用处理程序,并实现相关方法。您可以查看 github 上的基本身份验证示例:https://github.com/blowdart/idunno.Authentication,但这里有一个快速示例来显示自定义处理程序的要点。

public class BasicAuthenticationOptions : AuthenticationSchemeOptions
{
    public BasicAuthenticationOptions()
    {
    }
}

internal class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
    private const string _Scheme = "MyScheme";

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

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        string authorizationHeader = Request.Headers["Custom-Auth-Handler"];

        // create a ClaimsPrincipal from your header
        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, "My Name")
        };

        var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
        var ticket = new AuthenticationTicket(claimsPrincipal,
            new AuthenticationProperties { IsPersistent = false },
            Scheme.Name
        );
        
        return AuthenticateResult.Success(ticket);
    }
}

然后您可以在 Startup.cs 中注册您的新方案:

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme)
        .AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>("MyScheme", options => { /* configure options */ })
}

虽然这是一个旧线程,但由于我最近偶然遇到了同样的问题,我认为对内部结构进行更多阐述可能会对其他人有所帮助

简短的回答取决于您的服务类型和您的 API。您 不需要 在以下情况下调用 UseAuthentication

  1. 您实现自己的处理身份验证的中间件 - 无需在此详细说明。你自己处理一切,显然不需要额外的依赖
  2. 您不需要自动远程身份验证

远程认证

需要重定向到身份提供者的身份验证,例如 OpenID Connect。

是什么让它如此特别?

这些中间件需要关联不同的 http 调用。

初始调用首先由中间件处理,然后重定向到身份提供者(用户需要登录的地方),然后返回到中间件。 在这种情况下,中间件需要拥有请求并且不允许其他身份验证中间件参与该过程。

这是 middleware 代码的第一部分:

// Give any IAuthenticationRequestHandler schemes a chance to handle the request
var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
    var handler = await handlers.GetHandlerAsync(context, scheme.Name) as 
    IAuthenticationRequestHandler;
    if (handler != null && await handler.HandleRequestAsync()) 
    {
        return;
    }
}
  • 这当然是一个简化的解释,因为远程处理程序更复杂。目标最终是集中和解释中间件的行为

自动认证

运行 自动为默认方案进行身份验证。顾名思义,如果您定义了默认身份验证方案,那么与中间件关联的身份验证处理程序将始终 运行.

凭直觉,您会期望身份验证中间件首先 运行,特别是它们应该 运行 在 MVC 层(即控制器)之前。但是,这也意味着身份验证层不知道应该 运行 哪些控制器或这些控制器的授权要求,换句话说,它不知道授权策略是什么 [Authorize("Policy")] 它应该评估。

所以从逻辑上讲,我们想先评估策略,然后才 运行 验证逻辑。这就是为什么身份验证处理程序移入 ASP 2.* 成为通用服务而不与中间件耦合的原因。

但是,在某些情况下,无论您的策略如何,您总是希望身份验证处理程序 运行。在这种情况下,您可以定义默认身份验证方案,该方案将自动 运行.

这解释了 middleware 代码的第二部分:

var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
    var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
    if (result?.Principal != null)
    {
        context.User = result.Principal;
    }
}

如果您正在开发支持多种身份验证方案的 REST API 或者混合使用经过身份验证和未经过身份验证的控制器,那么您不需要自动身份验证,因为它增加了冗余。

结论

这给我们带来了有趣的问题和答案:当身份验证不是自动且非远程时,何时何地进行身份验证?

在正常的 MVC 授权流程中,这发生在调用 IAuthenticationService.AuthenticateAsync

的 AuthorizeFilter class 中
  • 如果您实现自己的授权层或使用较低级别的 APIs(例如未作为控制器实现的 websockets),您可以自己调用此方法

对于这些情况,不需要调用 UseAuthentication

来自 UseAuthentication 的 GitHub 源代码。

    public static IApplicationBuilder UseAuthentication(this IApplicationBuilder app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }
        
        return app.UseMiddleware<AuthenticationMiddleware>();
    }

如您所见,它只是添加了一个名为 AuthenticationMiddleware.

的中间件

这正是 AuthenticationMiddleware 正在做的事情:

 public async Task Invoke(HttpContext context)
        {
            context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
            {
                OriginalPath = context.Request.Path,
                OriginalPathBase = context.Request.PathBase
            });

            // Give any IAuthenticationRequestHandler schemes a chance to handle the request
            var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
            foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
            {
                var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
                if (handler != null && await handler.HandleRequestAsync())
                {
                    return;
                }
            }

            var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
            if (defaultAuthenticate != null)
            {
                var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
                if (result?.Principal != null)
                {
                    context.User = result.Principal;
                }
                if (result?.Succeeded ?? false)
                {
                    var authFeatures = new AuthenticationFeatures(result);
                    context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
                    context.Features.Set<IAuthenticateResultFeature>(authFeatures);
                }
            }

            await _next(context);
        }