A controller action which returns a partial view inserts the logon page when authorization fails

半城伤御伤魂 提交于 2019-11-29 20:29:05
Simen Echholt

Solution 1, return response indicating error


Pro ASP.NET MVC 2 Framework offers one solution for this

If an Ajax request is denied authorization, then usually you don’t want to return an HTTP redirection to the login page, because your client-side code is not expecting that and may do something unwanted such as injecting the entire login page into the middle of whatever page the user is on. Instead, you’ll want to send back a more useful signal to the client-side code, perhaps in JSON format, to explain that the request was not authorized. You could implement this as follows:

public class EnhancedAuthorizeAttribute : AuthorizeAttribute 
{ 
    protected override void HandleUnauthorizedRequest(AuthorizationContext context) 
    { 
        if (context.HttpContext.Request.IsAjaxRequest()) { 
            UrlHelper urlHelper = new UrlHelper(context.RequestContext); 
            context.Result = new JsonResult { 
                Data = new { 
                    Error = "NotAuthorized", 
                    LogOnUrl = urlHelper.Action("LogOn", "Account") 
                }, 
                JsonRequestBehavior = JsonRequestBehavior.AllowGet 
            }; 
        } 
        else 
            base.HandleUnauthorizedRequest(context); 
    } 
} 

You'd then get back a JSON object from the controller {'Error': 'NotAuthorized', 'LogonUrl': '...'} which you could then use to redirect the user.

And alternative response, if you're expecting HTML, could be to return a plain string, like NotAuthorized:<your_url> and check if the response matches this pattern.


Solution 2, return and handle 401 Unautorized status code


As this has to be checked on every ajax request's success callback, this gets quite tedious. It would be nice to be able to catch this case globally. The best solution would be to return a 401 Unauthorized status code from the server and use jQuery's .ajaxError to catch it, but this is problematic on IIS/ASP.NET, since it's deadly committed to redirecting you to the login page if the response has this statuscode. As long as the <authentication> tag is present in web.config this redirection will happen [1] if you don't do something about it.

So let's do something about it. This hacky way seemed nice. In your global.asax.cs

protected void Application_EndRequest()
{
    if (Context.Response.StatusCode == 302 && Context.Request.RequestContext.HttpContext.Request.IsAjaxRequest())
    {
        Context.Response.Clear();
        Context.Response.StatusCode = 401;
    }
}

(I'd love to hear it if anyone knows of any better methods to return a 401 status code.)

By doing this, you're preventing the default behaviour of redirecting to the logon page when the request is an ajax request. You can therefore use the default AuthorizeAttribute as it is, since Application_EndRequest takes care of the rest.

Now, in the jQuery code we'll use the .ajaxError function to catch the error. This is a global ajax event handler, meaning it will intercept every error made by any ajax call. It can be attached to any element - in my example I've just chosen body cause its always present

$("body").ajaxError(function(event, XMLHttpRequest, ajaxOptions, thrownError) {
    if (XMLHttpRequest.status == 401) {
        alert("unauthorized");
    }
});

This way you get your redirection logic centralized in one place, instead of having to check it on every damn ajax request success callback


Solution 3, return custom header


This answer offers an alternative solution. It uses the same kind of global event handler, but skips the hacky bits. It adds a custom header to the page if you're not authenticated and the request is an ajax request, and checks for this header on each .ajaxComplete. Note that the method provided in the linked answer is unsecure, as it will return the original view with possibly sensitive content and rely on javascript to redirect the user away from it. With a bit of modification it's quite awesome as it doesn't require any hacks and the logic can be centralized to one callback function. The only downside as I can see is that it doens't reply with the semantically correct status code, as it's actually not 200 OK because you're 401 Unauthorized


We can bake this into another custom EnhancedAuthorizationAttribute

public class EnhancedAuthorizeAttribute : AuthorizeAttribute
{
        protected override void HandleUnauthorizedRequest(AuthorizationContext context)
        {
            if (context.HttpContext.Request.IsAjaxRequest()) 
            {
                context.HttpContext.Response.AddHeader("REQUIRES_AUTH", "1");
                context.Result = new EmptyResult();
            }
            else
            {
                base.HandleUnauthorizedRequest(context);
            }
        }
    }
}

Now, every time an ajax request completes, you check for this header:

$('body').ajaxComplete(function(event,request,settings){
    if (request.getResponseHeader('REQUIRES_AUTH') === '1'){
        alert("unauthorized");
    };
});

For Simen Echholt second solution you can use

$.ajaxSetup({ global: true,
    statusCode: {
        401: function () {
            alert("unauthorized");
        }
    }
});

to bind some your function on status code 401

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!