为什么 AuthenticationHandler.HandleForbiddenAsync 完全忽略运行的代码?

Why does AuthenticationHandler.HandleForbiddenAsync Totally Ignore Code That Runs?

提问人:LFN 提问时间:1/23/2017 最后编辑:P. MagnussonLFN 更新时间:1/31/2023 访问量:4091

问:

谢谢你们抽出时间。 此实现有意不使用内置 Cookie、JWT 令牌或任何其他身份验证机制。请不要建议使用任何内置的 Auth 内容来绕过该问题。这是关于一个完全定制的身份验证/授权实现,用于诊断和了解正在发生的事情以及原因。这里的关键词是理解。

public class CustomAuthenticationHandler : AuthenticationHandler<CustomAuthenticationOptions> { 

    protected override Task<AuthenticateResult> HandleAuthenticateAsync() {

        // Do stuff and return appropriately after choosing to set the
        // authenticated principle or not.
    }

    protected override Task<bool> HandleUnauthorizedAsync(ChallengeContext context) {

        // This method name suggests that you can choose what happens in the event
        // that Authentication fails. Common sense suggests we should expect a 401
        // which we do see - but something in the internals ignores anything we
        // do here - instead returning a blank page with a 401 result.

        // Note that the event ALWAYS fires and code ALWAYS runs without errors when
        // Authentication fails but the code is entirely ignored (so our redirect 
        // doesnt happen).

        // Adding a specific "ActiveAuthenticationSchemes" proerty to the Controller Action gets the redirect to be honoured.

        // Why offer this override (or even invoke the event) if it will be totally ignored by default???
        // Why REQUIRE that every Authorization attribute must specify an ActiveAuthenticationSchemes
        // property when there is only 1 scheme running? Its madness for large complex applications.

        // This will be ignored by default
        Context.Response.StatusCode = (int)HttpStatusCode.Redirect;
        Context.Response.Redirect("/login?returnUrl=" + Options.UrlEncoder.Encode(httpRequest.Path + httpRequest.QueryString));

         // Return true to stop internals overriding our code? Nope - still ignored.
        return Task.FromResult(true);
    }

    protected override Task<bool> HandleForbiddenAsync(ChallengeContext context) {

        // This method name suggests that you can choose what happens in the event
        // that Authentication succeeds and Authorisation fails. We expect to see
        // a 403 Forbidden here. But again what we see is a blank page and a 401 Unathorized.
        // Something in the internals is ignoring this code and CHANGING what would
        // have been a sensible 403 result into a 401.

        // Like the HandleUnauthorizedAsync method, it is possible to get this
        // method code to be honoured if we explicitly set an ActiveAuthenticationSchemes
        // property on every Authorize attribute.

        // Isnt this again a bonkers requirement for complex applications?

        // This will be ignored in favour of a blank page and a 401
        // We can set either the StatusCode or do the redirect, both will be duly ignored
        Context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
        Context.Response.Redirect("/_403");

        // Return true to stop internals overriding our code? Nope - still ignored.
        return Task.FromResult(true);
    }
}

为了完整起见,启动如下所示:

public class Startup {

    public void ConfigureServices(IServiceCollection services) {

        services.AddMvc();
        services.AddDataProtection();
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {

        app.UseCustomAuthenticationMiddleware(

            new CustomAuthenticationOptions() {
                //AuthenticationScheme = "CustomAuthenticationScheme",
                AutomaticAuthenticate = true,
                AutomaticChallenge = true
            }
        );

        // -----

        app.UseMvc(routes => {
            routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

我们的中间件有一个简单的实现,它调用处理程序:

protected override AuthenticationHandler<CustomAuthenticationOptions> CreateHandler() {
    return new CustomAuthenticationHandler();
}

那么,是否有某种内置的内部故障保护来阻止人们在不对每个操作的每个 Authorize 属性显式设置 ActiveAuthenticationSchemes 的情况下连接自定义身份验证(以阻止我们这样做)?

我很困惑代码运行没有错误,但随后被忽略了。

关于如何自己诊断的建议和解释一样受欢迎 - 这让我发疯:) !!!

* 编辑/更新

可能的线索。似乎 [Authorize] 属性也有一些令人愤怒的奇怪行为。在操作级别应用“ActiveAuthenticationSchemes”时,对于“未经授权”和“禁止”的结果,事情开始如你所期望的那样工作。但是,将该属性移动到控制器级别,它仅对“未经授权”有效。禁止的处理程序代码再次被忽略(导致空白页和 401 而不是编码的重定向)。这里的内部有些东西正在踩踏明智的甚至编码的结果。不是吗?:) ...查看 MVC 源代码,看起来 MVC 可能会强制执行 401 而不是质询结果。我希望这不是真的......

* 编辑 2

看来,至少在一定程度上,这是真的。我创建了一个仅运行 MVC 的新项目(没有额外的身份验证或授权),并且无论您是否经过身份验证,MVC 都会返回 401 Unauthorized。感觉这应该是 403 禁止的,如果授权但权限不足。显然,身份验证和 MVC 之间没有连接某些东西,导致代码被忽略,行为被 MVC 401 结果覆盖。或。。。仍在调查中...

C# ASP.Net-Core 授权

评论

0赞 Tseng 1/23/2017
我认为这是因为你在某处有一个授权中间件,它可以捕获和处理它,如果你的方法中有的话,也许是 Identity。您是否使用完全相同的授权方案?.UseIdentity(...)Configure
0赞 LFN 1/23/2017
我没有使用身份、Cookie、令牌或任何其他类型的内置身份验证或授权工具。我故意试图理解看似有点奇怪的默认行为。我正在使用 MVC,所以也许可以返回 401 而不是挑战结果或其他东西?
0赞 Tseng 1/24/2017
您是否注册了其他保单,例如?options.AddPolicy("", builder => builder.RequireAuthenticatedUser());
0赞 LFN 1/24/2017
不,没有策略 - 所有代码都已发布。看起来它可能是 MVC,我会更新帖子。

答:

2赞 TeChaiMail 3/20/2019 #1

若要解决此问题,请改用 HandleChallengeAsync 方法:

protected override Task HandleChallengeAsync(AuthenticationProperties properties)       {           
    string redirectURL;


    if (ReadConfig.LoginURL_Exists && Request.AcceptHTML ())            
    {
        redirectURL = TextHelper.AppendVarValueToHTTPURL (ReadConfig.LoginURL, GeneralConstants.RETURN_URL, Request.GetDisplayUrl());

        Response.Redirect (redirectURL);

        return Task.CompletedTask;          
    }

    Response.StatusCode = (int) HttpStatusCode.Unauthorized;

    return Task.CompletedTask;      
}