问题
My MVC web application serves two types of users.
- First one over standard web browser;
- Second one over REST returning only JSON data.
Additionally,
- Both require Authentication and authorization;
- Both scenarios are differentiated based on the route so that I know what content to serve.
When users access the application, if they are not logged in, the application should react differently.
- In the first case it should return the default LogIn page (this is fine).
- In the second case it should return a 401 Unauthorized code only.
I'm used to working with WCF REST service where I could raise an exception like this:
throw new WebProtocolException(System.Net.HttpStatusCode.Unauthorized, exc.Message, exc);
and receive a 401 message. The problem with the same approach within MVC when I put the statusCode like this:
HttpContext.Response.StatusCode = (Int32)HttpStatusCode.Unauthorized
it always redirects to the LogIn page.
How can I do this?
I've tried overriding the AuthorizeAttribute and handling the OnAuthorization function, but still as soon as I set the statusCode to 401 it gets redirected to the LogIn page.
回答1:
To prevent login page redirection you must set SuppressFormsAuthenticationRedirect property of HttpContext.Response to true;
HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
回答2:
What you are experiencing is a hole in ASP.NET MVC (I hope they fix one day).
The standard operating model for ASP.NET is that if a 401 Http Status code is detected, then as you are experiencing, it automatically redirects to the login page, and this happens even if you have come in via an Ajax call. Unfortunately I have also not found any way to change this behaviour.
What I do instead is return an alternative, otherwise unused Http Status Code that I can detect in the client and handle in the appropriate manner.
Therefore within my Authentication Filter, if its an Ajax request I return 449 otherwise the standard 401. Then on the client I can examine the XMLHttpRequest.status and take appropriate action if 449 is detected.
回答3:
You can create a simple authorization attribute filter (extend the AuthorizeAttribute class) and use it for your access control. Then try something like this:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest() || string.Compare("GET", filterContext.HttpContext.Request.HttpMethod, true) != 0)
{
// Returns 403.
filterContext.Result = new HttpStatusCodeResult((int)HttpStatusCode.Forbidden);
}
else
{
// Returns 401.
filterContext.Result = new HttpUnauthorizedResult();
}
}
The effect is that, POST and AJAX requests will always receive a 403 response which makes it easier for you to handle your ajax submits in javascript. As for the non-ajax posts, it doesn't really matter what the response is because your user shouldn't have got his hands on the submit form in the first place :)
As for the other requests, the method returns 401 that the formsAuthentiction module will pick up and then redirect your response to the login page.
回答4:
This is the way I managed to prevent redirection to the login page.
In my case when I wanted to receive the status code in order to handle it in javascript :
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 302 && Context.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
Context.Response.Clear();
Context.Response.StatusCode = 401;
}
}
回答5:
I'm working on a web application which also exposes a web api and I don't see this behavior.
When you are using REST you need a mechanism to authenticate the user issuing the request for those endpoints/resources that are not public.
This mechanism can be either:
- Sending your credentials with every request. Usually using Http Basic Authentication.
- Expose an authentication endpoint that can be hit once with the proper credentials, and the user gets a token/ticket as a result, if the authentication succeeds.
If you are going for the second option then there are alternatives from simple to complex that would make your solution more or less robust. Amazon's way is well known and properly documented. However sometimes only sending back the token as a header using https is enough if your data is not that sensitive.
Regardless if you are using Basic HTTP Authentication or the most secure authentication mechanism in the world, you will need something that validates what you send on every request, and populates the Principal in the context of the request.
This something can be achieved in Asp.net Web Api with a Message Handler aka DelegatingHandler which will sit and process every request to your api controllers and populate your Principal or not, based on the authentication information it finds.
You can find an example of a DelegatingHandler Implementation here
After you have this infrastructure in place, then you should be able to use Syste.Web.Http.AuthorizeAttribute aka [Authorize] at the api controller level or at the action level, according to your needs.
You should get a 401 Unauthorized w/o a redirection, if the DelegatingHandler is not able to populate the principal given the auth info it has at hand.
Do not forget to register your DelegatingHandler on App_Start.
回答6:
You can user OnRedirectToLogin method of while configuring identity.
来源:https://stackoverflow.com/questions/3687578/how-can-mvc-return-unauthorized-code-without-redirecting-to-login-view