Подтвердить что ты не робот

AJAX и FormsAuthentication, как запрет FormsAuthentication отменяет HTTP 401?

В одном приложении, настроенном с помощью FormsAuthentication, когда пользователь получает доступ к файлу auth или с устаревшим на защищенную страницу, ASP.NET выдает HTTP 401 Unauthorized, тогда модуль FormsAuthentication перехватывает этот ответ до конца запроса и измените его для HTTP 302 Found, установив HTTP-заголовок "Location:/path/loginurl", чтобы перенаправить пользовательский агент на страницу входа в систему, затем браузер переходит на эту страницу и извлекает страницу входа, которая не защищена, получение HTTP 200 OK.

Это была очень хорошая идея, когда AJAX не рассматривался.

Теперь у меня есть url в моем приложении, которое возвращает данные JSON, и ему нужно, чтобы пользователь был аутентифицирован. Все работает хорошо, проблемы состоят в том, что, если истекает срок действия файла cookie auth, когда мой клиентский код вызывает сервер, он получит HTTP 200 OK с html страницы входа, а HTTP 401 Unauthorized (потому что объяснено ранее). Затем моя клиентская сторона пытается проанализировать страницу html входа в систему как json и не удается.

Возникает вопрос: как справиться с истекшей аутентификацией с клиентской стороны? Какое самое элегантное решение для решения этой ситуации? Мне нужно знать, когда вызов был успешным или нет, и я хотел бы сделать это, используя семантику HTTP.

Можно ли читать пользовательские заголовки HTTP с клиентской стороны в безопасном кросс-браузере? Есть ли способ сообщить FormsAuthenticationModule не выполнять перенаправления, если запрос является запросом AJAX? Есть ли способ переопределить статус HTTP с помощью HTTP-заголовка так же, как вы можете переопределить метод HTTP-запроса?

Мне нужна аутентификация Forms, и я бы хотел избежать перезаписи этого модуля или написать свой собственный модуль аутентификации формы.

С уважением.

4b9b3361

Ответ 1

У меня была такая же проблема, и мне пришлось использовать пользовательский атрибут в MVC. Вы можете легко адаптировать это для работы в веб-формах, вы можете переопределить авторизацию своих страниц на базовой странице, если все ваши страницы наследуются с некоторой базовой страницы (глобальный атрибут в MVC позволяет то же самое - переопределить метод OnAuthorization для всех контроллеров/действий в приложение)

Вот как выглядит атрибут:

public class AjaxAuthorizationAttribute : FilterAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest()
                && !filterContext.HttpContext.User.Identity.IsAuthenticated
                && (filterContext.ActionDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).Count() > 0
                || filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).Count() > 0))
            {
                filterContext.HttpContext.SkipAuthorization = true;
                filterContext.HttpContext.Response.Clear();
                filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
                filterContext.Result = new HttpUnauthorizedResult("Unauthorized");
                filterContext.Result.ExecuteResult(filterContext.Controller.ControllerContext);
                filterContext.HttpContext.Response.End();
            }
        }
    }

Обратите внимание, что вам нужно вызвать HttpContext.Response.End(); или ваш запрос будет перенаправлен на логин (я потерял некоторые из моих волос из-за этого).

На стороне клиента я использовал метод jQuery ajaxError:

var lastAjaxCall = { settings: null, jqXHR: null };
var loginUrl = "yourloginurl";

//...
//...

$(document).ready(function(){
    $(document).ajaxError(function (event, jqxhr, settings) {
            if (jqxhr.status == 401) {
                if (loginUrl) {
                    $("body").prepend("<div class='loginoverlay'><div class='full'></div><div class='iframe'><iframe id='login' src='" + loginUrl + "'></iframe></div></div>");
                    $("div.loginoverlay").show();
                    lastAjaxCall.jqXHR = jqxhr;
                    lastAjaxCall.settings = settings;
                }
            }
    }

}

Это показало логин в iframe по текущей странице (похоже, что пользователь перенаправлен, но вы можете сделать его другим), и когда логин был успешным, это всплывающее окно было закрыто, а оригинальный запрос ajax был повторно отправлен:

if (lastAjaxCall.settings) {
        $.ajax(lastAjaxCall.settings);
        lastAjaxCall.settings = null;
    }

Это позволяет вашим пользователям входить в систему при завершении сеанса, не теряя при этом никакой работы или данных, введенных в последней отображаемой форме.

Ответ 2

Я сильно краду этот ответ от других сообщений, но идея может заключаться в том, чтобы реализовать HttpModule to перехватить перенаправление в страницу входа (инструкции по этой ссылке).

Вы также можете изменить этот пример HttpModule только для перехвата перенаправления, если запрос был сделан через AJAX, если поведение по умолчанию правильное, если запрос не был выполнен с помощью AJAX:

Обнаружение вызова ajax, ASP.net

Итак, что-то вроде:

class AuthRedirectHandler : IHttpModule
{
    #region IHttpModule Members

    public void Dispose()
    {

    }

    public void Init(HttpApplication context)
    {
        context.EndRequest+= new EventHandler(context_EndRequest);
    }


    void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;
        if (app.Response.StatusCode == 302 
            && app.Request.Headers["X-Requested-With"] == "XMLHttpRequest"
            && context.Response.RedirectLocation.ToUpper().Contains("LOGIN.ASPX"))
        {
            app.Response.ClearHeaders();
            app.Response.ClearContent();
            app.Response.StatusCode = 401;
        }
    }

    #endregion
}

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

Затем вы просто добавите в свой web.config:

  <httpModules>
    <add name="AuthRedirectHandler" type="SomeNameSpace.AuthRedirectHandler, SomeNameSpace" />
  </httpModules>

Так или иначе. Опять же, настоящая оригинальная мысль попала в этот ответ, я просто вытаскиваю различные биты из SO и других частей сети.

Ответ 3

У меня возникли проблемы с принятым ответом . В основном, мои журналы ошибок заполнялись ошибками Server cannot set status after HTTP headers have been sent.

Я попытался выполнить принятый ответ, чтобы задать вопрос Сервер не может установить статус после отправки HTTP-заголовков IIS7.5, опять же никакого успеха.

Попытавшись немного, я наткнулся на SuppressFormsAuthenticationRedirect свойство

Если ваша .Net-версия равнa >= 4.5, вы можете добавить следующий код к методу HandleUnauthorizedRequest своего пользовательского класса AuthorizeAttribute.

public sealed class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
            base.HandleUnauthorizedRequest(filterContext);
            return;
        }

        base.HandleUnauthorizedRequest(filterContext);
        return;
    }
}

Важной частью является блок if. Это самая простая задача, если вы находитесь на .Net 4.5 и уже имеете специальную авторизацию.