Перенаправление входа в членство в ASP.NET при несанкционированном доступе к пользователю

У нас есть приложение, которое использует членство ASP.net для предоставления основных механизмов входа. Все работает отлично, но недавно мы обнаружили, что если вы попытаетесь перейти на страницу входа во время входа в систему, вы перенаправлены на страницу "Несанкционированная".

Пример потока пользователей.

Пользователь переходит на защищенную страницу (для всего приложения требуется логин, есть даже домашняя страница, которую вы можете посетить, просто перенаправляет прямо для входа в систему). Это перенаправляет их на https://www.example.com/Account/Login.

Пользователь регистрируется и перенаправляется на домашнюю страницу https://www.example.com/. Они вошли в систему и все работает нормально.

Пользователь нажимает закладку, для которой установлено значение https://www.example.com/Account/Login

Пользователь перенаправляется на общую Неавторизованную страницу.

У меня есть атрибут <Authorize()> на моем AccountController, но атрибут <AllowAnonymous()> в действии "Вход", который, как мы видели ранее, отлично работает, когда вы не вошли в систему, но когда вы, похоже, немного путаницы.

AccountController

<Authorize()> _
Public Class AccountController
'''other functions go here'''

<AllowAnonymous()> _
Public Function Login(ByVal returnUrl As String) As ActionResult
    ViewData("ReturnUrl") = returnUrl
    Return View()
End Function

Фильтр AuthorizeRedirect

<AttributeUsage(AttributeTargets.[Class] Or AttributeTargets.Method)> _
Public Class AuthorizeRedirect
    Inherits AuthorizeAttribute
    Private Const IS_AUTHORIZED As String = "isAuthorized"

    Public RedirectUrl As String = "~/Home/Unauthorized"

    Protected Overrides Function AuthorizeCore(httpContext As System.Web.HttpContextBase) As Boolean
        Dim isAuthorized As Boolean = MyBase.AuthorizeCore(httpContext)

        httpContext.Items.Add(IS_AUTHORIZED, isAuthorized)

        Return isAuthorized
    End Function

    Public Overrides Sub OnAuthorization(filterContext As AuthorizationContext)
        MyBase.OnAuthorization(filterContext)

        Dim isAuthorized = If(filterContext.HttpContext.Items(IS_AUTHORIZED) IsNot Nothing, Convert.ToBoolean(filterContext.HttpContext.Items(IS_AUTHORIZED)), False)

        If Not isAuthorized AndAlso filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated Then
            filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl)
        End If
    End Sub
End Class

Увидев все это, я подумал, что самым простым решением было бы проверить, был ли пользователь уже зарегистрирован в моем действии для входа и перенаправил его сам, что-то вроде этого.

<AllowAnonymous()> _
Public Function Login(ByVal returnUrl As String) As ActionResult
    If User.Identity.IsAuthenticated() Then
        Return RedirectToAction("Index", "Home")
    End If
    ViewData("ReturnUrl") = returnUrl
    Return View()
End Function

Но AuthorizeFilter всегда прыгает с первого взгляда, что понятно, но я не могу понять последний недостающий кусок. Все, что я хочу, это не показывать "У вас нет разрешения на просмотр этой страницы", если пользователь переходит на экран входа во время входа в систему и скорее перенаправляет их на домашнюю страницу. Что мне не хватает?


Изменить, чтобы сделать вещи более ясными

Когда вы вошли в систему, я перехожу к /Account/Login. Этот 302 перенаправляет меня на /Home/Unauthorized (моя пользовательская страница). Тем не менее, я все еще зарегистрирован.

Сетевые запросы

Сетевой запрос на страницу входа, который 302 перенаправляет на Неавторизованный

Несанкционированная страница. Обратите внимание, что выделенные желтые разделы показывают, что я все еще зарегистрирован. Это появляется только в том случае, если вы вошли в систему. Когда вы не вошли в систему, вы ничего не получите.

Несанкционированная страница

Проблема заключается в том, что приложение не знает, что делать, когда я уже зарегистрировался и попытаюсь перейти на страницу с атрибутом [AllowAnonymous]. Если что-нибудь, поведение, которое я вижу здесь, предпочтительнее, это на самом деле дает мне страницу входа еще раз, потому что это будет путать, но тем не менее, это не идеально.


Редактировать 2 - Выполнение строки кода по строке

Ниже приведены результаты перехода по коду по строкам.

Страница /Account/Login во время входа.

Первая точка останова в OnAuthorization sub в AuthorizeRedirect.

Public Overrides Sub OnAuthorization(filterContext As AuthorizationContext)
    MyBase.OnAuthorization(filterContext)

    Dim isAuthorized = If(filterContext.HttpContext.Items(IS_AUTHORIZED) IsNot Nothing, Convert.ToBoolean(filterContext.HttpContext.Items(IS_AUTHORIZED)), False)

    If Not isAuthorized AndAlso  filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated Then
        filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl)
    End If
End Sub

Строка, начинающаяся с Dim isAuthorized, возвращает False. filterContext.HttpContext.Items(IS_AUTHORIZED) ничего (не существует в списке элементов).

Это значит, что следующий оператор If имеет значение True (Not isAuthorized AndAlso... IsAuthenticated), в результате чего перенаправление на RedirectUrl.

После этого происходит возврат к тем же шагам, за исключением того, что на этот раз он вычисляется как false, то есть перенаправление не происходит, хотя я предполагаю, что это просто "неавторизованная" загрузка страницы и ее запуск тот же код снова.

Я попытался добавить следующий блок в начало Login функции AccountController.

    If User.Identity.IsAuthenticated() Then
        Return RedirectToAction("Index", "Home")
    End If

Но, конечно, поскольку фильтр запускается до того, как действия происходят, этот код не попадает до тех пор, пока он не перенаправит меня на Unauthorized (проверяется путем перехода).

Ответ 1

Базовый класс для AuthorizationAttribute имеет этот код в методе OnAuthorization:

bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
                         || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);

if (skipAuthorization)
{
    return;
}

if (AuthorizeCore(filterContext.HttpContext))
// ...

Как таковое, если действие контроллера имеет AllowAnonymousAttribute, определенный на нем, ваш метод AuthorizeCore не будет вызван.

Из-за этого filterContext.HttpContext.Items(IS_AUTHORIZED) никогда не будет установлен.

Вы можете просто скопировать код из здесь для реализации OnAuthorization без вызова базового класса. Таким образом, вы можете иметь дело с кэшированием, в котором вы когда-либо хотите.

Кстати, у меня создалось впечатление, что если авторизация завершилась неудачей, то более поздний процесс в конвейере запроса все равно перенаправил на страницу входа в систему. Вот почему базовая реализация OnAuthorization устанавливает filterContext.Result в новый экземпляр HttpUnauthorizedResult. Поэтому не совсем понятно, почему вы переопределяете OnAuthorization и выполняете перенаправление в первую очередь. Если вы хотите, чтобы какой-то пользовательский код авторизации просто возвращал true или false из AuthorizeCore, должно быть достаточно.