Как перенаправить на динамический URL входа в ASP.NET MVC

Я создаю многопользовательский веб-сайт, на котором размещаются страницы для клиентов. Первым сегментом URL-адреса будет строка, которая идентифицирует клиента, определенный в Global.asax, используя следующую схему маршрутизации URL-адресов:

"{client}/{controller}/{action}/{id}"

Это работает отлично, с URL-адресами, такими как /foo/Home/Index.

Однако при использовании атрибута [Авторизовать] я хочу перенаправить на страницу входа, которая также использует ту же схему сопоставления. Таким образом, если клиент является foo, страница входа в систему будет /foo/Account/Login вместо фиксированной пересылки /Account/Login, определенной в web.config.

MVC использует HttpUnauthorizedResult для возврата неавторизованного статуса 401, который, как я полагаю, вызывает перенаправление ASP.NET на страницу, определенную в web.config.

Так кто-нибудь знает, как переопределить поведение перенаправления входа в ASP.NET? Или было бы лучше перенаправить в MVC, создав собственный атрибут авторизации?

РЕДАКТИРОВАТЬ - ответ: после некоторого поиска в источнике .Net, я решил, что лучший атрибут проверки подлинности -

public class ClientAuthorizeAttribute: AuthorizeAttribute
{
    public override void OnAuthorization( AuthorizationContext filterContext )
    {
        base.OnAuthorization( filterContext );

        if (filterContext.Cancel && filterContext.Result is HttpUnauthorizedResult )
        {
            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary
                {
                    { "client", filterContext.RouteData.Values[ "client" ] },
                    { "controller", "Account" },
                    { "action", "Login" },
                    { "ReturnUrl", filterContext.HttpContext.Request.RawUrl }
                });
        }
    }
}

Ответ 1

Я думаю, что основная проблема заключается в том, что если вы собираетесь копировать встроенный класс ASP.NET FormsAuthentication (и нет никакой веской причины вам не следует), то в конце дня собирается позвонить FormsAuthentication.RedirectToLoginPage(), который будет искать один сконфигурированный URL. Там только один URL-адрес для входа, и именно так они его разработали.

Мой удар по проблеме (возможно, реализация Rube Goldberg) позволил бы перенаправить ее на одну страницу входа в корневой каталог, общий для всех клиентов, например /account/login. На этой странице входа ничего не будет видно; он проверяет либо параметр ReturnUrl, либо какое-то значение, которое у меня есть в сеансе, или файл cookie, который идентифицирует клиента, и использует это для немедленного перенаправления 302 на конкретную страницу/клиент/учетную запись/логин. Это дополнительный перенаправление, но, вероятно, не заметно и позволяет использовать встроенные механизмы перенаправления.

Другой вариант заключается в создании вашего собственного настраиваемого атрибута по мере того, как вы описываете, и избегаете всего, что вызывает метод RedirectToLoginPage() в классе FormsAuthentication, так как вы замените его своей логикой перенаправления. (Вы можете создать свой собственный класс, который похож.) Поскольку это статический класс, я не знаю какого-либо механизма, с помощью которого вы могли бы просто ввести свой собственный альтернативный интерфейс и волшебным образом работать с существующим атрибутом [Авторизовать], который удары, но люди делали подобные вещи до.

Надеюсь, что это поможет!

Ответ 2

В RTM-версии ASP.NET MVC отсутствует свойство Cancel. Этот код работает с ASP.NET MVC RTM:

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Resources;

namespace ePegasus.Web.ActionFilters
{
    public class CustomAuthorize : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            base.OnAuthorization(filterContext);
            if (filterContext.Result is HttpUnauthorizedResult)
            {
                filterContext.Result = new RedirectToRouteResult(
                    new System.Web.Routing.RouteValueDictionary
                        {
                                { "langCode", filterContext.RouteData.Values[ "langCode" ] },
                                { "controller", "Account" },
                                { "action", "Login" },
                                { "ReturnUrl", filterContext.HttpContext.Request.RawUrl }
                        });
            }
        }
    }
}

Изменить: Возможно, вы захотите отключить аутентификацию по умолчанию для форм loginUrl в web.config - если кто-то забудет ваш пользовательский атрибут и по ошибке использует встроенный атрибут [Authorize].

Измените значение в web.config:

 <forms loginUrl="~/Account/ERROR" timeout="2880" />

Затем создайте метод действия "ERROR", который регистрирует ошибку и перенаправляет пользователя на самую общую страницу входа.

Ответ 3

Моим решением этой проблемы был пользовательский класс ActionResult:

    sealed public class RequiresLoginResult : ActionResult
    {
        override public void ExecuteResult (ControllerContext context)
        {
            var response = context.HttpContext.Response;

            var url = FormsAuthentication.LoginUrl;
            if (!string.IsNullOrWhiteSpace (url))
                url += "?returnUrl=" + HttpUtility.UrlEncode (ReturnUrl);

            response.Clear ();
            response.StatusCode = 302;
            response.RedirectLocation = url;
        }

        public RequiresLoginResult (string returnUrl = null)
        {
            ReturnUrl = returnUrl;
        }

        string ReturnUrl { get; set; }
    }