Проверка подлинности с помощью форм: отключить перенаправление на страницу входа - PullRequest
66 голосов
/ 15 мая 2010

У меня есть приложение, которое использует проверку подлинности с помощью форм ASP.NET.По большей части это работает отлично, но я пытаюсь добавить поддержку простого API через файл .ashx.Я хочу, чтобы в файле ashx была необязательная аутентификация (т. Е. Если вы не указали заголовок Authentication, он просто работает анонимно).Но, в зависимости от того, что вы делаете, я хочу требовать аутентификацию при определенных условиях.

Я подумал, что будет просто ответить на код состояния 401, если требуемая аутентификация не была предоставлена, но похоже, чтоМодуль проверки подлинности форм перехватывает это и отвечает перенаправлением на страницу входа.Я имею в виду, что если мой ProcessRequest метод выглядит следующим образом:

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

Тогда вместо того, чтобы получить код ошибки 401 на клиенте, как я и ожидал, я на самом деле получение перенаправления 302. На страницу входа в систему.

Для обычного HTTP-трафика я вижу, как это было бы полезно, но для моей страницы API я хочу, чтобы 401 прошел без изменений, чтобы клиентский клиентвместо этого может ответить на него программно.

Есть ли способ сделать это?

Ответы [ 11 ]

71 голосов
/ 31 мая 2013

ASP.NET 4.5 добавил логическое HttpResponse.SuppressFormsAuthenticationRedirect свойство.

public void ProcessRequest(HttpContext context)
{
    Response.StatusCode = 401;
    Response.StatusDescription = "Authentication required";
    Response.SuppressFormsAuthenticationRedirect = true;
}
35 голосов
/ 15 мая 2010

После небольшого исследования похоже, что FormsAuthenticationModule добавляет обработчик для события HttpApplicationContext.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;
    }
}

На самом деле все немного сложнее, но я в основном получил код, посмотрев на реализацию FormsAuthenticationModule в отражателе. Моя реализация отличается от встроенной FormsAuthenticationModule тем, что она ничего не делает, если вы отвечаете 401 - никакого перенаправления на страницу входа вообще. Я думаю, если это когда-нибудь станет требованием, я могу поместить элемент в контекст, чтобы отключить автоматическое перенаправление или что-то еще.

11 голосов
/ 03 ноября 2010

Я не уверен, что это будет работать для всех, но в IIS7 вы можете вызвать Response.End () после того, как вы установили код состояния и описание. Таким образом, это # ​​& $ ^ # @ *! FormsAuthenticationModule не будет выполнять перенаправление.

public void ProcessRequest(HttpContext context) {
    Response.StatusCode = 401;
    Response.StatusDescription = "Authentication required";
    Response.End();
}
7 голосов
/ 04 января 2014

Чтобы немного расширить ответ Захаридля, я использовал это, чтобы решить мои проблемы. При каждом запросе, в начале, если это AJAX, немедленно подавляйте поведение.

protected void Application_BeginRequest()
{
    HttpRequestBase request = new HttpRequestWrapper(Context.Request);
    if (request.IsAjaxRequest())
    {
        Context.Response.SuppressFormsAuthenticationRedirect = true;
    }
}
5 голосов
/ 06 января 2011

Я знаю, что уже есть ответ с галочкой, но при попытке решить подобную проблему я нашел этот (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, см. этот вопрос и ответ .

5 голосов
/ 30 ноября 2010

Я не знаю, как этот Response.End () работал для вас. Я попробовал это без радости, затем посмотрел на MSDN для Response.End (): «останавливает выполнение страницы и вызывает событие EndRequest».

Для чего это стоило, мой взлом был:

_response.StatusCode = 401;
_context.Items["401Override"] = true;
_response.End();

Затем в Global.cs добавьте обработчик EndRequest (который будет вызываться после HTTPModule для аутентификации):

protected void Application_EndRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.Items["401Override"] != null)
    {
        HttpContext.Current.Response.Clear();
        HttpContext.Current.Response.StatusCode = 401;
    }
}
4 голосов
/ 15 ноября 2010

То, что вы узнали, правильно о том, что формы auth перехватывают 401 и выполняют перенаправление, но мы также можем сделать это, чтобы полностью изменить это.

В основном вам нужен модуль http для перехвата перенаправления 302 на страницу входа и обратного преобразования в 401.

Шаги, описанные в здесь

Данная ссылка относится к службе WCF, но она одинакова во всех сценариях авторизации форм.

Как объяснено в приведенной выше ссылке, вам также необходимо очистить заголовки http, но не забудьте поместить заголовок cookie обратно в ответ, если исходный ответ (т.е. до перехвата) содержал какие-либо файлы cookie.

2 голосов
/ 13 декабря 2011

Это известная проблема, и для этого есть пакет NuGet и / или исходный код .

0 голосов
/ 11 марта 2019

Забавный хак, если вы используете .NET Framework> = v4.0, но отражение , чтобы установить значение недоступного SuppressFormsAuthenticationRedirect свойства:

// Set property to "true" using reflection
Response
  .GetType()
  .GetProperty("SuppressFormsAuthenticationRedirect")
  .SetValue(Response, true, null);
0 голосов
/ 30 ноября 2012

Вы не устанавливаете заголовок WWW-Authenticate в отображаемом коде, поэтому клиент не может выполнять HTTP-аутентификацию вместо аутентификации по формам. Если это так, вы должны использовать 403 вместо 401, что не будет перехвачено FormsAuthenticaitonModule.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...