开发者

How do you handle ajax requests when user is not authenticated?

开发者 https://www.devze.com 2022-12-25 12:52 出处:网络
How do you hand开发者_如何学JAVAle ajax requests when user is not authenticated? Someone enters the page, leaves room for an hour, returns, adds comment on the page that goes throuh ajax using jQuer

How do you hand开发者_如何学JAVAle ajax requests when user is not authenticated?

Someone enters the page, leaves room for an hour, returns, adds comment on the page that goes throuh ajax using jQuery ($.post). Since he is not authenticated, method return RedirectToRoute result (redirects to login page). What do you do with it? How do you handle it on client side and how do you handle it in controller?


EDIT:

I wrote above answer a long time ago and now I believe that sending 403 is not proper way to go. 403 has slightly different meaning and it just shouldn't be used. This is corrected attribute using 401. It differs only with additional context.HttpContext.Response.End() in Http401Result and different HTTP code:

public class OptionalAuthorizeAttribute : AuthorizeAttribute
{
    private class Http401Result : ActionResult
    {
        public override void ExecuteResult(ControllerContext context)
        {
            // Set the response code to 401.
            context.HttpContext.Response.StatusCode = 401;
            context.HttpContext.Response.Write(CTRes.AuthorizationLostPleaseLogOutAndLogInAgainToContinue);
            context.HttpContext.Response.End();
        }
    }

    private readonly bool _authorize;

    public OptionalAuthorizeAttribute()
    {
        _authorize = true;
    }

    //OptionalAuthorize is turned on on base controller class, so it has to be turned off on some controller. 
    //That is why parameter is introduced.
    public OptionalAuthorizeAttribute(bool authorize)
    {
        _authorize = authorize;
    }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        //When authorize parameter is set to false, not authorization should be performed.
        if (!_authorize)
            return true;

        var result = base.AuthorizeCore(httpContext);

        return result;
    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
        {
            //Ajax request doesn't return to login page, it just returns 401 error.
            filterContext.Result = new Http401Result();
        }
        else
            base.HandleUnauthorizedRequest(filterContext);
    }
}

OLD ANSWER:

While I like the ideas posted in other answers (which I had an idea about earlier), I needed code samples. Here they are:

Modified Authorize attribute:

public class OptionalAuthorizeAttribute : AuthorizeAttribute
{
    private class Http403Result : ActionResult
    {
        public override void ExecuteResult(ControllerContext context)
        {
            // Set the response code to 403.
            context.HttpContext.Response.StatusCode = 403;
            context.HttpContext.Response.Write(CTRes.AuthorizationLostPleaseLogOutAndLogInAgainToContinue);
        }
    }

    private readonly bool _authorize;

    public OptionalAuthorizeAttribute()
    {
        _authorize = true;
    }

    //OptionalAuthorize is turned on on base controller class, so it has to be turned off on some controller. 
    //That is why parameter is introduced.
    public OptionalAuthorizeAttribute(bool authorize)
    {
        _authorize = authorize;
    }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        //When authorize parameter is set to false, not authorization should be performed.
        if (!_authorize)
            return true;

        var result = base.AuthorizeCore(httpContext);

        return result;
    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
        {
            //Ajax request doesn't return to login page, it just returns 403 error.
            filterContext.Result = new Http403Result();
        }
        else
            base.HandleUnauthorizedRequest(filterContext);
    }
}

HandleUnauthorizedRequest is overridden, so it returns Http403Result when using Ajax. Http403Result changes StatusCode to 403 and returns message to the user in response. There is some additional logic in attribute (authorize parameter), because I turn on [Authorize] in the base controller and disable it in some pages.

The other important part is global handling of this response on client side. This is what I placed in Site.Master:

<script type="text/javascript">
    $(document).ready(
        function() {
            $("body").ajaxError(
                function(e,request) {
                    if (request.status == 403) {
                        alert(request.responseText);
                        window.location = '/Logout';
                    }
                }
            );
        }
    );
</script>

I place a GLOBAL ajax error handler and when ever $.post fails with a 403 error, the response message is alerted and the user is redirected to logout page. Now I don't have to handle the error in every $.post request, because it is handled globally.

Why 403, and not 401? 401 is handled internally by MVC framework (that is why redirection to login page is done after failed authorization).

What do you think about it?


The idea I came up with when a coworker asked about how to handle it was this - make an AuthorizeAjax attribute. It can interrogate and verify that Request.IsAjaxRequest() and, if the request isn't authenticated, return a specific JSON error object. It's possible you could simply override the default AuthorizeAttribute and have it call the base unless it's an unauthorized AJAX request so you don't have to worry about whether to tag controller actions with [Authorize] or [AuthorizeAjax].

On the client-side, all your pages would have to be equipped to deal with the returned error, but that logic can likely be shared.


I would propose creating your own AuthorizeAttribute and if the request is an Ajax request, throw an HttpException(401/403). And also switch to use jQuery's Ajax Method instead.

Assuming you've implemented error pages and they return the correct status code, the error callback will be executed instead of the success callback. This will be happen because of the response code.


The simplest and cleanest solution I've found for this is to register a callback with the jQuery.ajaxSuccess() event and check for the "X-AspNetMvc-Version" response header.

Every jQuery Ajax request in my app is handled by Mvc so if the header is missing I know my request has been redirected to the login page, and I simply reload the page for a top-level redirect:

 $(document).ajaxSuccess(function(event, XMLHttpRequest, ajaxOptions) {
    // if request returns non MVC page reload because this means the user 
    // session has expired
    var mvcHeaderName = "X-AspNetMvc-Version";
    var mvcHeaderValue = XMLHttpRequest.getResponseHeader(mvcHeaderName);

    if (!mvcHeaderValue) {
        location.reload();
    }
});

The page reload may cause some Javascript errors (depending on what you're doing with the Ajax response) but in most cases where debugging is off the user will never see these.

If you don't want to use the built-in header I'm sure you could easily add a custom one and follow the same pattern.


Here's a solution I use. It is dead simple, if a bit brute-force. I like it because I'm lazy and I don't want to think about special attributes on action methods and I don't want to write ajax error handlers if I don't have to (although there's no reason client script couldn't detect the 403 status code and do something user friendly).

Putting this in Global.axax detects any unauthenticated ajax request and simply returns 403, with no content. This prevents unauthenticated ajax calls getting redirected to the login form when forms authentication is in use.

 protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
        // Prevent Ajax requests from being returned the login form when not authenticated
        // (eg. after authentication timeout).
        if ((Request.Headers["X-Requested-With"] != null && Request.Headers["X-Requested-With"] == "XMLHttpRequest")
            ||
            (Request["X-Requested-With"] != null && Request["X-Requested-With"] == "XMLHttpRequest"))
        {
            if (!Request.IsAuthenticated)
            {
                Response.Clear();
                Response.StatusCode = 403;
                Response.Flush();
                Response.End();
            }
        }
    }


You can detect ajax request and send 401, and on client side you can even show an ajax dialog with login prompt, after which you can "continue" your failed ajax request and make your application work and user feel like session timeout never happened. See this answer for details.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号