ASP.NET Core 5.0 WebAPI 中的多种身份验证方案

Posted

技术标签:

【中文标题】ASP.NET Core 5.0 WebAPI 中的多种身份验证方案【英文标题】:Multiple authentication schemes in ASP.NET Core 5.0 WebAPI 【发布时间】:2022-01-04 08:19:03 【问题描述】:

我有一整套在 .NET 5.0 中开发的 (ASP.NET Core) Web API,并实现了 Cookie 和 OpenIdConnect 身份验证方案。 使用 Azure AD 成功验证(用户 ID 和密码)后,会生成 cookie 并存储用户权限等。

现在,我想使用基于 API 密钥的身份验证(通过请求标头中的 api-key)向第三方消费者公开同一组 API。 我开发了一个自定义身份验证处理程序,如下所示。

using Microsoft.AspNetCore.Authentication;

namespace Management.Deployment.Securities.Authentication

  public class ApiKeyAuthenticationSchemeOptions : AuthenticationSchemeOptions
  

  



namespace Management.Deployment.Securities.Authentication

  public static class ApiKeyAuthenticationDefaults
  
    public static readonly string AuthenticationScheme = "ApiKey";
    public static readonly string DisplayName = "ApiKey Authentication Scheme";
  

ApiKeyAuthenticationHandler 定义如下,直截了当,如果请求标头包含有效的 api 密钥,则添加权限声明(分配给 api 密钥)并将身份验证标记为成功,否则失败。

using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Management.Securities.Authorization;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace Management.Deployment.Securities.Authentication

  public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationSchemeOptions>
  
    private const string APIKEY_NAME = "x-api-key";
    private const string APIKEY_VALUE = "sdflasuowerposaddfsadf1121234kjdsflkj";

    public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationSchemeOptions> options,
                                       ILoggerFactory logger,
                                       UrlEncoder encoder,
                                       ISystemClock clock) : base(options, logger, encoder, clock)
    

    

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    
      string extractedApiKey = Request.Headers[APIKEY_NAME];

      if (!APIKEY_VALUE.Equals(extractedApiKey))
      
        return Task.FromResult(AuthenticateResult.Fail("Unauthorized client."));
      

      var claims = new[]
      
        new Claim("Permissions", "23")
      ;

      var claimsIdentity = new ClaimsIdentity(claims, nameof(ApiKeyAuthenticationHandler));
      var authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), Scheme.Name);

      return Task.FromResult(AuthenticateResult.Success(authenticationTicket));

    
  

我还定义了 ApiKeyAuthenticationExtensions 如下。

using Microsoft.AspNetCore.Authentication;
using Management.Deployment.Securities.Authentication;
using System;

namespace Microsoft.Extensions.DependencyInjection

  public static class ApiKeyAuthenticationExtensions
  
    public static AuthenticationBuilder AddApiKey(this AuthenticationBuilder builder)
    
      return builder.AddScheme<ApiKeyAuthenticationSchemeOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationDefaults.AuthenticationScheme, ApiKeyAuthenticationDefaults.DisplayName, x =>  );
    

    public static AuthenticationBuilder AddApiKey(this AuthenticationBuilder builder, Action<ApiKeyAuthenticationSchemeOptions> configureOptions)
    
      return builder.AddScheme<ApiKeyAuthenticationSchemeOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationDefaults.AuthenticationScheme, ApiKeyAuthenticationDefaults.DisplayName, configureOptions);
    
  

Startup.cs 中 ConfigureServices() 的略读版本在这里。请注意我使用了 ForwardDefaultSelector。

public void ConfigureServices(IServiceCollection services)
        
            IAuthCookieValidate cookieEvent = new AuthCookieValidate();

            services.AddAuthentication(options =>
            
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            )
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
            
                options.Cookie.Name = ".Mgnt.AspNetCore.Cookies";
                options.ExpireTimeSpan = TimeSpan.FromDays(1);
                options.Events = new CookieAuthenticationEvents
                
                    OnRedirectToAccessDenied = context =>
                    
                        context.Response.StatusCode = 403;
                        return Task.FromResult(0);
                    ,
                    OnRedirectToLogin = context =>
                    
                        context.Response.StatusCode = 401;
                        return Task.FromResult(0);
                    ,
                    OnValidatePrincipal = cookieEvent.ValidateAsync
                ;
                options.ForwardDefaultSelector = context =>
                
                    return context.Request.Headers.ContainsKey(ApiConstants.APIKEY_NAME) ? ApiKeyAuthenticationDefaults.AuthenticationScheme : CookieAuthenticationDefaults.AuthenticationScheme;
                ;
                options.Cookie.HttpOnly = true;
                options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
            )
            .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => Configuration.Bind(OpenIdConnectDefaults.AuthenticationScheme, options))
            .AddApiKey();

            services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
            
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("email");
            );
            

            services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
            services.AddSingleton<IAuthorizationHandler, PermissionHandler>();

        

配置方法如下。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            

            app.UseHsts();
            app.Use((context, next) =>
            
                context.Request.Scheme = "https";
                return next();
            );

            app.UseRouting();

            app.UseAuthentication();

            app.UseAuthorization();

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

当我在请求标头中发送正确的 apikey 时,自定义处理程序将返回成功作为身份验证结果,并进一步处理请求。

但如果传递了错误的 api 密钥,它不会返回身份验证失败消息 - “未经授权的客户端。”。而是进一步处理请求并发送附加的响应。

为解决此问题需要进行哪些更改,以便 api 返回身份验证失败消息 - “未经授权的客户端”。并停止进一步处理请求?

【问题讨论】:

【参考方案1】:

如果您打算使用 apikeys,那么您只能靠自己,并且(据我所知)没有内置的对 API-keys 的直接支持。但是,内置了对基于 JWT 的访问令牌的支持,我建议您也将它用于想要访问您的 api 的外部第三方。也许使用客户端凭据流。

如需帮助,请参阅http://codingsonata.com/secure-asp-net-core-web-api-using-api-key-authentication/

我还认为您应该配置并让授权处理程序负责决定谁可以访问服务。

见Policy-based authorization in ASP.NET Core

【讨论】:

我已经用实施细节更新了我的问题。请看一看。 查看我的更新答案

以上是关于ASP.NET Core 5.0 WebAPI 中的多种身份验证方案的主要内容,如果未能解决你的问题,请参考以下文章

[WebApi]ASP.Net Core 中使用JWT认证(3.1版本,5.0也可以使用)

在 ASP.NET Core 5.0 Web API 中实现 DelegatingHandler?

如何按照存储库模式在 Asp.Net Core 5.0 项目上实现 .Net Core Identity?

ASP.NET Core 5.0中的Host.CreateDefaultBuilder执行过程

即使在 asp.net core 5.0 中提供了不记名令牌,也会返回 401 [关闭]

如何在 Asp.Net Core 3.0 WebAPI 中启用 CORS