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

时间:2017-01-23 12:25:10

标签: asp.net-core authorization

感谢您的时间。 此实现有意不使用内置cookie,JWT令牌或任何其他Auth机制。请不要建议使用任何内置的Auth功能来绕过这个问题。这是一个完全定制的身份验证/授权实现,用于诊断和了解正在发生的疯狂和原因。这里的关键词是理解。

<div class="js html container" data-clipboard-target="#\<h1\>">
	<code class="html syntax" id="<h1>">
		<span class="html line">
			<span class="html comment">&lt;!-- Heading level 1 --&gt;</span>
		</span>
		<span class="html line">
			&lt;<span class="html tag">h1</span>&gt;Heading level 1&lt;<span class="html tag">/h1</span>&gt;
		</span>
	</code>
</div>

为了完整起见,启动看起来像这样:

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?}");
        });
    }
}

那么,是否存在某种内置的内部故障保护措施来阻止人们连接自定义身份验证而不在每个Action上的每个Authorize属性上显式设置ActiveAuthenticationSchemes(以阻止我们这样做)?

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

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

*编辑/更新

可能的线索。似乎[Authorize]属性也有一些令人愤怒的奇怪行为。在Action级别应用“ActiveAuthenticationSchemes”时,事情就像您对Unauthorized和Forbidden结果所期望的那样开始工作。将该属性移动到控制器级别,但它仅对Unauthorized有效。禁止处理程序代码再次被忽略(导致空白页面和401而不是编码重定向)。这里内部的东西在整个合理甚至编码的结果上都是st脚。不是吗? :) ...看看MVC源代码,它看起来像MVC可能强制401而不是挑战结果。我希望那不是真的......

*编辑2

它似乎至少在某种程度上是真实的。我创建了一个只运行MVC的新项目(没有额外的身份验证或授权),无论您是否经过身份验证,MVC都会返回401 Unauthorized。感觉这应该是403 Forbidden如果授权但具有不充分的权限。并且显然在Authentication和MVC之间没有连线,导致代码被忽略,并且MVC 401结果覆盖了行为。也许......还在调查......

1 个答案:

答案 0 :(得分: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;      
}