AJAX и FormsAuthentication, как предотвратить FormsAuthentication переопределяет HTTP 401? - PullRequest
26 голосов
/ 23 сентября 2011

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

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

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

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

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

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

С уважением.

Ответы [ 3 ]

23 голосов
/ 02 октября 2011

У меня была такая же проблема, и мне пришлось использовать пользовательский атрибут в 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;
    }

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

5 голосов
/ 22 июля 2013

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

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

Немного погуглив, я наткнулся на свойство SuppressFormsAuthenticationRedirect

Если ваша версия .Net>> 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 и у вас уже есть пользовательская авторизация.

5 голосов
/ 26 сентября 2011

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

Вы также можете изменить этот пример 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 и других частей сети.

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