【问题标题】:Multiple authentication schemes in ASP.NET Core 5.0 WebAPIASP.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 返回身份验证失败消息 - “未经授权的客户端”。并停止进一步处理请求?

【问题讨论】:

    标签: asp.net-core .net-core asp.net-core-webapi asp.net-core-5.0 asp.net-core-authenticationhandler


    【解决方案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

    【讨论】:

    • 我已经用实施细节更新了我的问题。请看一看。
    • 查看我的更新答案
    猜你喜欢
    • 2021-10-18
    • 2019-08-31
    • 1970-01-01
    • 1970-01-01
    • 2022-07-27
    • 2018-02-23
    • 2019-06-13
    • 2018-03-23
    • 2019-07-10
    相关资源
    最近更新 更多