窗体身份验证: 禁用重定向到登录页

我有一个使用 ASP.NET 窗体身份验证的应用程序。在大多数情况下,它工作得很好,但是我试图通过一个。Ashx 文件。我希望 ashx 文件具有可选的身份验证(也就是说,如果您不提供身份验证头,那么它只能匿名工作)。但是,根据您所做的工作,我希望在某些条件下要求身份验证。

我认为,如果没有提供所需的身份验证,那么使用状态码401进行响应是一件简单的事情,但是看起来表单身份验证模块正在拦截这个身份验证,并以重定向到登录页面的方式进行响应。我的意思是,如果我的 ProcessRequest方法是这样的:

public void ProcessRequest(HttpContext context)
{
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
}

然后不像我期望的那样在客户机上得到401错误代码,而是 事实上得到一个302重定向到登录页面。

对于普通的 HTTP 流量,我可以看到这是多么有用,但是对于我的 API 页面,我希望401未经修改地通过,以便客户端调用者能够以编程方式响应它。

有什么办法吗?

46215 次浏览

configuration\authentication中查看 Web.config 文件内部。 如果有一个具有 loginUrl属性的 forms子元素,请删除它,然后再试一次。

经过一些调查之后,看起来 表格认证模组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;
}
}

实际上比这稍微复杂一些,但是我基本上是通过查看 FormsAuthenticationModule在反射器中的实现来获得代码的。我的实现不同于内置的 FormsAuthenticationModule,因为它不会做任何事情,如果您响应一个401-根本没有重定向到登录页面。我想如果这成为一个需求,我可以在上下文中添加一个项目来禁用自动重定向之类的东西。

我不确定这是否适用于所有人,但在 IIS7中,您可以调用响应。在设置了状态代码和描述之后使用 End ()。这边,那边!FormsAuthenticationModule 不会执行重定向。

public void ProcessRequest(HttpContext context) {
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
Response.End();
}

你们发现的关于 Auth 拦截401并进行重定向的表单是正确的,但我们也可以做到这一点来逆转。

基本上,您需要的是一个 http 模块来拦截302重定向到登录页面并将其反转到401。

这方面的步骤在 给你中有解释

给定的链接是关于 WCF 服务的,但是在所有表单认证场景中都是相同的。

正如在上面的链接中所解释的,你也需要清除 http 头,但是记住,如果原始响应(即在拦截之前)包含任何 cookie,就要把 cookie 头放回到响应中。

我不知道那个响应。End ()为你工作。我毫无兴趣地尝试了一下,然后查看了 MSDN 的响应。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)。

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 身份验证,而是执行 Forms 身份验证。如果是这种情况,那么应该使用403而不是401,因为 FormsAuthenticaitonModule不会拦截401。

NET 4.5添加了布尔 HttpResponse.SuppressFormsAuthenticationRedirect属性。

public void ProcessRequest(HttpContext context)
{
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
Response.SuppressFormsAuthenticationRedirect = true;
}

为了稍微建立扎卡里德尔的回答,我用这个来解决我的困境。在每个请求开始时,如果是 AJAX,立即抑制该行为。

protected void Application_BeginRequest()
{
HttpRequestBase request = new HttpRequestWrapper(Context.Request);
if (request.IsAjaxRequest())
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
}

如果使用.NET Framework > = v4.0但是 < v4.5,那么这种做法很有趣。它使用 反思来设置不可访问的 SuppressFormsAuthenticationRedirect属性的值:

// Set property to "true" using reflection
Response
.GetType()
.GetProperty("SuppressFormsAuthenticationRedirect")
.SetValue(Response, true, null);

我遇到的问题是,我不仅想避免重定向,而且还想避免表单身份验证本身,以使 Web API 工作。在 web.config 中带有 api 位置标记的条目没有帮助。 因此,我使用 SuppressFormAuthenticationRedirect 和 HttpContext.Current. SkipAuthorization 来抑制一般的身份验证。 为了识别我使用的发件人,例如 Header 中的 UserAgent,当然建议执行进一步的认证步骤,例如检查发件人的 IP 地址或随请求一起发送另一个密钥。 下面是在 Global.asax.cs 中插入的。

protected void Application_BeginRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Request.UserAgent == "SECRET-AGENT")
{
AppLog.Log("Redirect suppressed");


HttpApplication context = (HttpApplication)sender;


context.Response.SuppressFormsAuthenticationRedirect = true;
HttpContext.Current.SkipAuthorization = true;
}
}

为了将用户重定向到未授权页面而不是登录页面,Hack 将在全局中实现 Application _ EndRequest,并检查响应状态代码302,这是从当前调用操作的临时重定向。

protected void Application_EndRequest(object sender, EventArgs e)
{
if(HttpContext.Current.Response.StatusCode == 302 && User.Identity.IsAuthenticated)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.Redirect("/UnauthorizedPageUrl");
}
}