表单身份验证:禁用重定向到登录页面


71

我有一个使用ASP.NET表单身份验证的应用程序。在大多数情况下,它工作得很好,但是我试图通过.ashx文件添加对简单API的支持。我希望ashx文件具有可选的身份验证(即,如果您不提供Authentication标头,则它只能匿名运行)。但是,根据您的操作,我希望在某些条件下要求身份验证。

我认为,如果未提供所需的身份验证,那么用状态码401进行响应将是一个简单的问题,但是似乎表单身份验证模块正在拦截该响应并以重定向到登录页面的方式进行响应。我的意思是,如果我的ProcessRequest方法如下所示:

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

然后,而不是像我期望的那样在客户端上获得401错误代码,实际上是获得302重定向到登录页面的信息。

对于正常的HTTP流量,我可以看到它如何有用,但是对于我的API页面,我希望401进行不修改的操作,以便客户端调用程序可以通过编程方式对其进行响应。

有什么办法吗?

Answers:


75

ASP.NET 4.5添加了BooleanHttpResponse.SuppressFormsAuthenticationRedirect属性。

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

这是使用.NET 4.5的任何人的答案。太糟糕了,我没有在列表中读到这么多,我不得不自己在反编译的源代码中找到它!
凯文·

3
您可以将其放在Global.asaxApplication_EndRequest事件中,以将其应用于整个应用程序。例如。
David Sherret

4
我在Application_BeginRequest方法中使用了类似的代码片段,并取得了巨大的成功。请求是AJAX吗?立即设置标志。
Tyler Forsythe 2014年

1
谢谢,我现在将其标记为已接受的答案,因为这肯定看起来像现在要走的路。
迪恩·哈丁2014年

在从FormsAuth迁移到ASP.NET Identity的过程中发现这很有用,在该过程中我需要同时激活两个对象
Simon_Weaver

35

经过一番调查,看起来FormsAuthenticationModuleHttpApplicationContext.EndRequest事件添加了一个处理程序。在它的处理程序中,它检查401状态代码,并基本上执行一个Response.Redirect(loginUrl)。据我所知,如果要使用,则无法覆盖此行为FormsAuthenticationModule

我最终解决该问题的方法是通过FormsAuthenticationModule在web.config中禁用,如下所示:

<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;
    }
}

它实际上比这稍微复杂一些,但是我基本上是通过查看inReflector的实现来获得代码的FormsAuthenticationModule。我的实现与内置的实现不同,FormsAuthenticationModule如果您使用401进行响应,则它不会执行任何操作-根本不会重定向到登录页面。我想如果有必要,我可以在上下文中放置一个项目以禁用自动重定向或其他功能。


2
注意:上面zacharydl回答是在ASP.NET 4.5+中的使用方式
Dean Harding

11

我不确定这是否对所有人都适用,但是在IIS7中,您可以在设置状态代码和描述后调用Response.End()。这样,#&$ ^#@ *!FormsAuthenticationModule不会进行重定向。

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

3
好约翰!我们必须付出多大的努力来解决这个问题,这可笑吗?HttpContext具有SkipAuthorization属性,如果它也具有SkipLoginRedirect,那就太好了。或者,是否可以通过目录对表单身份验证进行范围划分和覆盖。
路加·桑普森

当我尝试此操作时,我使用的是IIS 6,并且我认为它在IIS 7和IIS 6中的工作方式有所不同(即在IIS 6中,Response.End()只是抛出了ThreadAbortException表单身份验证模块正在捕获的-表示它仍在进行逻辑重定向为了我)。
Dean Harding

2
此方法有效,但是无论如何,控件将转到FormsAuthenticationModule,并且其EndRequest事件处理程序将尝试执行重定向,并且由于已对响应进行了End()编辑,因此会触发异常,该异常记录在系统事件日志中,也Application_Error()称为。
Sharptooth 2013年

7

为了稍微建立在zacharydl的答案上​​,我用它解决了我的麻烦。在每个请求的开头,如果是AJAX,请立即取消该行为。

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

5

我不知道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;
    }
}

在页面中,我使用StatusCode=418了替代文字并将其添加到中StatusDescription。然后,我在EndRequest事件中捕获了这些事件,并在其中重置了响应。如果我在页面中使用状态码401,它仍然会被重定向。
史蒂夫·劳滕施拉格

4

您发现关于auth拦截401并进行重定向的表单是正确的,但是我们也可以这样做来扭转这种情况。

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

此处说明了执行此操作的步骤

给定的链接是关于WCF服务的,但是在所有表单身份验证方案中都是相同的。

如以上链接中所述,您还需要清除http标头,但是请记住,如果原始响应(即在拦截之前)包含任何cookie,则将cookie标头放回响应中。


嗯,是的,这是一个有趣的想法!当然,我已经有了适合我的解决方案,但是我敢肯定其他人可能会发现这是一个更好的解决方案。谢谢!
Dean Harding

似乎是骇客,可能会有类似怪异的副作用!
艾伦·克里斯滕森

4

我知道已经有一个关于滴答声的答案,但是在尝试解决类似问题时,我发现了这个问题(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");

然后在EndRequest事件发生时使用HTTP模块对其进行处理,以将代码重写回401。

HttpApplication app = (HttpApplication)sender;
if (app.Context.Response.StatusCode == 418)
{
    app.Context.Response.StatusCode = 401;
}

浏览器/客户端将收到正确的内容和状态代码,对我来说非常有用:)

如果您有兴趣了解有关HTTP状态代码418的更多信息,请参阅此问题和答案


大!它的工作方式就像我希望它能起作用:)而是添加专用的http模块,我使用了“ global.asax”事件EndRequest。可以挂接覆盖“ Init”方法的此事件
Pavlo Neiman

感谢您使用自己的状态码进行提及:由于我只想为通过AJAX调用测试身份验证的单个页面返回401,因此我能够Response.StatusCode将该页面上的“失败”更改为418,以避免所有其他解决方法!IIS7抱怨,但是没有人需要访问该页面。
Kodithic


1

如果使用.NET Framework> = v4.0但<v4.5,则很有趣。它使用反射来设置不可访问SuppressFormsAuthenticationRedirect属性的值:

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

0

您没有WWW-Authenticate在显示的代码中设置标题,因此客户端无法执行HTTP身份验证,而不是表单身份验证。在这种情况下,您应该使用403而不是401,它不会被拦截FormsAuthenticaitonModule


0

我遇到的问题是,我不仅要避免重定向,还要避免表单身份验证本身,以使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;                
        }
    }

-2

在中查看Web.config文件的内部configuration\authentication。如果那里forms有带有loginUrl属性的子元素,请将其删除并重试。


2
删除刚更改的URL即可重定向到它,/login.aspx而不是我的自定义URL 。无论如何,我还是要为“普通”页面保留“表单身份验证”,只为这个我要更改的.ashx文件。
Dean Harding
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.