表单身份validation:禁用redirect到login页面
我有一个使用ASP.NET窗体身份validation的应用程序。 大多数情况下,它工作的很好,但我试图通过.ashx文件添加对简单API的支持。 我希望ashx文件具有可选的身份validation(即,如果您不提供身份validation头,那么它只是匿名工作)。 但是,根据您的具体情况,我想要在某些条件下进行身份validation。
我认为这将是一个简单的问题,如果没有提供所需的身份validation,但状态码为401,但似乎表单身份validation模块正在拦截,并以redirect到login页面进行响应。 我的意思是,如果我的ProcessRequest
方法如下所示:
public void ProcessRequest(HttpContext context) { Response.StatusCode = 401; Response.StatusDescription = "Authentication required"; }
然后,而不是像客户端上得到一个401错误代码,我实际上得到一个302redirect到login页面。
对于正常的HTTPstream量,我可以看到这将是有用的,但是对于我的API页面,我希望401不经过修改,以便客户端调用者可以以编程方式对其进行响应。
有没有办法做到这一点?
ASP.NET 4.5添加了布尔HttpResponse.SuppressFormsAuthenticationRedirect
属性。
public void ProcessRequest(HttpContext context) { Response.StatusCode = 401; Response.StatusDescription = "Authentication required"; Response.SuppressFormsAuthenticationRedirect = true; }
经过一番调查,看起来FormsAuthenticationModule为HttpApplicationContext.EndRequest
事件添加了一个处理程序。 在它的处理程序中,它检查一个401状态码,基本上做一个Response.Redirect(loginUrl)
。 据我所知,如果要使用FormsAuthenticationModule
,则无法重写此行为。
我最终解决的方法是禁用web.config中的FormsAuthenticationModule
,如下所示:
<authentication mode="None" />
然后自己实现Application_AuthenticateEvent
:
void Application_AuthenticateRequest(object sender, EventArgs e) { if (Context.User == null) { var oldTicket = ExtractTicketFromCookie(Context, FormsAuthentication.FormsCookieName); if (oldTicket != null && !oldTicket.Expired) { var ticket = oldTicket; if (FormsAuthentication.SlidingExpiration) { ticket = FormsAuthentication.RenewTicketIfOld(oldTicket); if (ticket == null) return; } Context.User = new GenericPrincipal(new FormsIdentity(ticket), new string[0]); if (ticket != oldTicket) { // update the cookie since we've refreshed the ticket string cookieValue = FormsAuthentication.Encrypt(ticket); var cookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName] ?? new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { Path = ticket.CookiePath }; if (ticket.IsPersistent) cookie.Expires = ticket.Expiration; cookie.Value = cookieValue; cookie.Secure = FormsAuthentication.RequireSSL; cookie.HttpOnly = true; if (FormsAuthentication.CookieDomain != null) cookie.Domain = FormsAuthentication.CookieDomain; Context.Response.Cookies.Remove(cookie.Name); Context.Response.Cookies.Add(cookie); } } } } private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name) { FormsAuthenticationTicket ticket = null; string encryptedTicket = null; var cookie = context.Request.Cookies[name]; if (cookie != null) { encryptedTicket = cookie.Value; } if (!string.IsNullOrEmpty(encryptedTicket)) { try { ticket = FormsAuthentication.Decrypt(encryptedTicket); } catch { context.Request.Cookies.Remove(name); } if (ticket != null && !ticket.Expired) { return ticket; } // if the ticket is expired then remove it context.Request.Cookies.Remove(name); return null; } }
实际上它比这个稍微复杂一点,但是我基本上通过在reflection器中查看FormsAuthenticationModule
的实现来获得代码。 我的实现不同于内置的FormsAuthenticationModule
,因为如果您使用401进行响应,则不会执行任何操作 – 根本不会redirect到login页面。 我猜如果这是一个要求,我可以把一个项目在上下文中禁用自动redirect或什么的。
我不确定这是否适用于所有人,但在IIS7中,您可以在设置状态码和说明后调用Response.End()。 这样,#&$ ^#@ *! FormsAuthenticationModule不会做redirect。
public void ProcessRequest(HttpContext context) { Response.StatusCode = 401; Response.StatusDescription = "Authentication required"; Response.End(); }
稍微build立在zacharydl的答案,我用这个来解决我的困境。 在每一个请求,一开始,如果是AJAX,马上抑制行为。
protected void Application_BeginRequest() { HttpRequestBase request = new HttpRequestWrapper(Context.Request); if (request.IsAjaxRequest()) { Context.Response.SuppressFormsAuthenticationRedirect = true; } }
你发现了什么是auth拦截401和做redirect的forms是正确的,但我们也可以做到这一点,以扭转这一点。
基本上你需要的是一个http模块拦截302redirect到login页面,并将其反转到401。
这样做的步骤在这里解释
给定的链接是关于一个WCF服务,但在所有forms的身份validationscheme是相同的。
正如在上面的链接中所解释的,你还需要清除http标题,但如果原始响应(即拦截之前)包含任何cookie,请记住将cookie标头放回响应。
我不知道如何Response.End()为你工作。 我试了一下没有快乐,然后看了MSDN的Response.End():'停止执行页面,并引发EndRequest事件'。
我值得一提的是:
_response.StatusCode = 401; _context.Items["401Override"] = true; _response.End();
然后在Global.cs中添加一个EndRequest处理程序(它将在Authentication HTTPModule之后被调用):
protected void Application_EndRequest(object sender, EventArgs e) { if (HttpContext.Current.Items["401Override"] != null) { HttpContext.Current.Response.Clear(); HttpContext.Current.Response.StatusCode = 401; } }
我知道已经有一个答案,但同时试图解决类似的问题,我发现这 ( http://blog.inedo.com/2010/10/12/http-418-im-a-teapot-finally-a- %e2%80%9clegitimate%e2%80%9d-use / )作为替代。
基本上你在你的代码中返回你自己的HTTP状态码(例如418)。 在我的情况下,一个WCF数据服务。
throw new DataServiceException(418, "401 Unauthorized");
然后使用HTTP模块在EndRequest
事件中处理它,将代码重写为401。
HttpApplication app = (HttpApplication)sender; if (app.Context.Response.StatusCode == 418) { app.Context.Response.StatusCode = 401; }
浏览器/客户端将收到正确的内容和状态代码,它对我很好:)
如果您有兴趣了解更多关于HTTP状态码418的信息,请参阅这个问题和答案 。
这是一个已知的问题,并且有一个NuGet包和/或可用的源代码 。
您不会在所显示的代码中设置WWW-Authenticate
标头,因此客户端无法执行HTTP身份validation而不是表单身份validation。 如果是这种情况,你应该使用403而不是401,这将不会被FormsAuthenticaitonModule
拦截。
在configuration\authentication
查看你的Web.config文件。 如果有一个带有loginUrl
属性的forms
子元素,请将其删除loginUrl
试。