如何让失败的 AuthorizeAttribute 返回 HTTP401?

Posted

技术标签:

【中文标题】如何让失败的 AuthorizeAttribute 返回 HTTP401?【英文标题】:How to get failed AuthorizeAttribute to return HTTP401? 【发布时间】:2020-04-17 18:08:40 【问题描述】:

我正在尝试向 ASP.NET Core 3.1 Web 服务添加身份验证,该服务会查找特定的自定义请求标头:

[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase

    [HttpPut]
    [Authorize(Policy = "MustSupplyAuthenticationToken")]
    public async Task<ActionResult> putMyStuff()
    
        // ...

我已将“MustSupplyAuthenticationToken”配置为使用我的 AuthenticationTokenRequirement 类,并连接 IAuthorizationHandler 以使用我的 AuthenticationTokenHandler(并连接 HttpContextFactory,因为我将需要它。):

public void ConfigureServices(IServiceCollection services)

    services.AddControllers();
    services.AddHttpContextAccessor();

    services.AddAuthorization(options =>
    
        options.AddPolicy("MustSupplyAuthenticationToken", 
            policy => policy.Requirements.Add(new AuthenticationTokenRequirement("MY_SECRET_TOKEN")));
    );

    services.AddTransient<IAuthorizationHandler, AuthenticationTokenHandler>();

我的 AuthenticationTokenRequirement 很简单:

public class AuthenticationTokenRequirement : IAuthorizationRequirement

    public readonly string authenticationToken;

    public AuthenticationTokenRequirement(string authenticationToken)
    
        this.authenticationToken = authenticationToken;
    

而我的 AuthenticationTokenHandler 并没有那么复杂:

public class AuthenticationTokenHandler : AuthorizationHandler<AuthenticationTokenRequirement>

    private readonly IHttpContextAccessor httpContextAccessor;

    public AuthenticationTokenHandler(IHttpContextAccessor httpContextAccessor)
    
        this.httpContextAccessor = httpContextAccessor;
    

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
        AuthenticationTokenRequirement requirement)
    
        var httpContext = this.httpContextAccessor.HttpContext;
        var request = httpContext.Request;

        var authenticationToken = request.getRequestHeader("authenticationToken");

        if (authenticationToken == null)
            context.Fail();
        else if (authenticationToken != requirement.authenticationToken)
            context.Fail();
        else
            context.Succeed(requirement);

        return Task.CompletedTask;
    

令人惊讶的是,这一切都奏效了。 HandleRequirementAsync() 被调用,当我调用 context.Fail() 访问被拒绝,当我调用 context.Succeed() 访问被允许。

唯一的问题是,当我调用 context.Fail() 时,响应返回 HTTP 500 - 我需要它返回 HTTP 401。(当我开始工作时,我需要返回 403 的不同策略。)

我是不是做错了什么,因为其他错误而得到 500?

还是失败的身份验证策略应该返回 500?

我需要做什么才能让它返回 401?


FWIW:我在服务器日志中看到了这一点:

2019-12-27T15:49:04.2221595-06:00 80000045-0001-f900-b63f-84710c7967bb [ERR] An unhandled exception has occurred while executing the request. (48a46595)
System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
   at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.<Invoke>g__AwaitMatcher|8_0(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task`1 matcherTask)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

这可能就是我看到 500 的原因。

但只有当我调用 context.Fail() 时。当我调用 context.Succeed() 时,我没有。

那么,当我未能满足要求时,为什么会收到“未指定身份验证方案”?

【问题讨论】:

看看这个:***.com/questions/52008000/…. 【参考方案1】:

好的,因为我正在尝试做的策略根本不是正确的方法。

我需要做的是配置一个身份验证方案。

在提取了上面所有的授权策略后,我添加了这个。

在 AuthorizeAttribute 中,我指定了一个身份验证方案:

[HttpPut]
[Authorize(AuthenticationSchemes = "AuthenticationTokenScheme")]
public async Task<ActionResult> putTicketDeliveryModel()

    // ...

在 ConfigureServices 中,我添加了方案:

services.AddAuthentication()
    .AddScheme<AuthenticationTokenOptions, AuthenticationTokenHandler>("AuthenticationTokenScheme", _ =>  );

然后我实现了我的 AuthenticationTokenOptions 和 AuthenticationTokenHandler 类:

public class AuthenticationTokenOptions : AuthenticationSchemeOptions



public class AuthenticationTokenHandler : AuthenticationHandler<AuthenticationTokenOptions>

    private readonly string expectedAuthenticationToken;

    public AuthenticationTokenHandler(IOptionsMonitor<AuthenticationTokenOptions> optionsMonitor,
        ILoggerFactory loggerFactory, UrlEncoder urlEncoder, ISystemClock systemClock,
        IConfiguration config)
        : base(optionsMonitor, loggerFactory, urlEncoder, systemClock)
    
        this.expectedAuthenticationToken = config.GetSection("ExpectedAuthenticationToken").Get<string>();
    

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    
        var authenticationToken = this.Request.getRequestHeader("authenticationToken");

        if (string.IsNullOrWhiteSpace(authenticationToken))
            return Task.FromResult(AuthenticateResult.NoResult());

        if (this.expectedAuthenticationToken != authenticationToken)
            return Task.FromResult(AuthenticateResult.Fail("Unknown Client"));

        var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(Enumerable.Empty<Claim>(), Scheme.Name));
        var authenticationTicket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);

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

如果我传递了错误的令牌,这将返回 401。

还没想好怎么返回403。

【讨论】:

以上是关于如何让失败的 AuthorizeAttribute 返回 HTTP401?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法让 AuthorizeAttribute 响应状态码 403 Forbidden 而不是重定向?

如何绕过 System.Web.Http.AuthorizeAttribute.IsAuthorized

ASP.NET Core 中如何通过 AuthorizeAttribute 做自定义验证?

两个(几乎)并发 DbContext 导致问题:如何在 Controller 和 AuthorizeAttribute 之间共享

WebAPI的AuthorizeAttribute扩展类中获取POST提交的数据

是否可以在 MVC 4 AuthorizeAttribute 中使用异步/等待?