ASP.NET Core 3.1 的自定义身份验证处理程序授权失败?

Authorization failing for custom authentication handler for ASP.NET Core 3.1?

我正在尝试实现一个简单的 api 基于密钥的身份验证处理程序。我的处理程序方法是

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
    // Get the apiKey from a store...
    if (apiKey != header.Parameter)
    {
        var error = "Invalid username or api key.";
        return Task.FromResult(AuthenticateResult.Fail(error));
    }

    var claims = new List<Claim> {new Claim("user", (string)username)};
    var identity = new ClaimsIdentity(claims);
    var principal = new ClaimsPrincipal(identity);
    var ticket = new AuthenticationTicket(principal, header.Scheme);

    return Task.FromResult(AuthenticateResult.Success(ticket));
}

当我使用正确的用户名和 api 密钥发出请求时,returns AuthenticateResult.Success(ticket) 上面的方法如预期的那样。但是,尽管经过了正确的身份验证,但我的控制器操作并未被调用。相反,Task HandleChallengeAsync(AuthenticationProperties properties) 被调用并返回 401 未经授权的响应。

我正在启动时注册我的身份验证处理程序 class 例如:

public void ConfigureServices(IServiceCollection services)
{
    // register controllers, etc.
    services.AddAuthentication("ApiKey").AddApiKeyBearer();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
    IHostApplicationLifetime applicationLifetime)
{
    app.ConfigureExceptionHandler()
            .UseRouting()
            .UseAuthentication()
            .UseAuthorization()
            .UseEndpoints(builder => builder.MapControllers());
}

认证已经成功,如何避免质询?

我认为如果没有更多的代码,可能有点难以查明问题所在。我创建了一个应该根据您的要求工作的示例。看看下面的例子。

编辑: 如果您不想手动配置以下设置,您可以查看以下库: https://github.com/mihirdilip/aspnetcore-authentication-apiKey/tree/3.1.1

处理程序

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

namespace WebApplication1.Handlers
{

    public class ApiKeyAuthenticationSchemeOptions
      : AuthenticationSchemeOptions
    { }

    public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationSchemeOptions>
    {
        //TODO Change to whatever name you want to use
        private const string ApiKeyHeaderName = "X-Token";

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

        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey(ApiKeyHeaderName))
            {
                return Task.FromResult(AuthenticateResult.Fail("Header was not found"));
            }

            string token = Request.Headers[ApiKeyHeaderName].ToString();

            //TODO Replace with proper token handling code
            if (token == "secret")
            {
                Claim[] claims = new[] {
                    new Claim(ClaimTypes.NameIdentifier, "john123"),
                    new Claim(ClaimTypes.Email, "john@gmail.com"),
                };

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

                return Task.FromResult(AuthenticateResult.Success(ticket));
            }
            else
            {
                return Task.FromResult(AuthenticateResult.Fail("Token is invalid"));
            }
        }
    }
}

启动

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using WebApplication1.Handlers;

namespace WebApplication1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication("ApiKey").AddScheme<ApiKeyAuthenticationSchemeOptions, ApiKeyAuthenticationHandler>("ApiKey", op => { });
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

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

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

控制器

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Linq;

namespace WebApplication1.Controllers
{
    [Route("api/students")]
    [ApiController]
    public class StudentsController : ControllerBase
    {

        [HttpGet("secret")]
        [Authorize]
        public IActionResult GetData()
        {
            string email = User.Claims.ElementAt(1).Value;
            return Ok("Secret data");
        }

        [HttpGet("public")]
        public IActionResult GetData2()
        {
            return Ok("Public data");
        }

    }
}

我找到了答案here。基本上,我需要像这样

覆盖启动 class 中的默认授权策略
services.AddAuthorization(o =>
{
    var builder = new AuthorizationPolicyBuilder("ApiKey");
    builder = builder.RequireClaim("user");
    o.DefaultPolicy = builder.Build();
});