ASP.NET MVC - Как показать несанкционированную ошибку на странице входа?

В моем приложении ASP.NET MVC у меня есть большинство контроллеров, украшенных

[Authorize(Roles="SomeGroup")]

Когда пользователь не имеет права доступа к чему-либо, они отправляются в "~/Login", который является действием "Вход" на моем контроллере учетной записи.

Как я могу определить, что пользователь достиг страницы входа из-за не авторизации, чтобы я мог показать соответствующую ошибку?

Ответ 1

Вы можете найти значение ?ReturnUrl= querystring или создать собственный фильтр авторизации и установить поле в TempData, указывающее причину.

Вот простой пользовательский фильтр, который сделает трюк:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{

    // NOTE: This is not thread safe, it is much better to store this
    // value in HttpContext.Items.  See Ben Cull answer below for an example.
    private bool _isAuthorized;

    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        _isAuthorized = base.AuthorizeCore(httpContext);
        return _isAuthorized;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);

        if(!_isAuthorized)
        {
            filterContext.Controller.TempData.Add("RedirectReason", "Unauthorized");
        }
    }
}

Тогда, на ваш взгляд, вы можете сделать что-то вроде этого:

@if(TempData["RedirectReason"] == "Unauthorized")
{
    <b>You don't have permission to access that area</b>
}

(Хотя я бы рекомендовал лучший подход, чем эти магические строки, но вы поняли смысл)

Ответ 2

ОБНОВЛЕНИЕ (июн 2015): @daniel-lidström правильно указал, что вы не должны использовать Response.Redirect в приложении ASP.NET MVC. Для получения дополнительной информации о том, почему, пожалуйста, см. Эту ссылку: Response.Redirect и ASP.NET MVC - не смешивать.

ОБНОВЛЕНИЕ (сентябрь 2014): Я не уверен, когда HandleUnauthorizedRequest был добавлен в AuthorizeAttribute, но в любом случае мне удалось уточнить код AuthorizeRedirect во что-то меньшее и простое.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirect : AuthorizeAttribute
{
    public string RedirectUrl = "~/Error/Unauthorized";

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        base.HandleUnauthorizedRequest(filterContext);

        if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new RedirectResult(RedirectUrl);
        }
    }
}

Оригинальный ответ Ниже (все еще полностью функциональный)

Я оставил этот ответ здесь, поскольку он все же дает вам представление о том, как работает конвейер авторизации.

Для тех, кто все еще приземляется здесь, я отредактировал ответ Бен Шейрман для автоматического перенаправления на несанкционированную страницу, когда пользователь вошел в систему, но не авторизовался. Вы можете изменить путь перенаправления, используя параметр name RedirectUrl.

EDIT: Я сделал решение потокобезопасным благодаря советам Tarynn и MSDN

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirect : AuthorizeAttribute
{
    private const string IS_AUTHORIZED = "isAuthorized";

    public string RedirectUrl = "~/error/unauthorized";

    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        bool isAuthorized = base.AuthorizeCore(httpContext);

        httpContext.Items.Add(IS_AUTHORIZED, isAuthorized);

        return isAuthorized;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);

        var isAuthorized = filterContext.HttpContext.Items[IS_AUTHORIZED] != null 
            ? Convert.ToBoolean(filterContext.HttpContext.Items[IS_AUTHORIZED]) 
            : false;

        if (!isAuthorized && filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl);
        }
    }
}

Ответ 3

Метод Бен Кулла работает хорошо, но помните, что есть два класса AuthorizeAttribute - один в System.Web.HTTP(используется Web API), а другой в System.Web.Mvc. Метод Бен использует класс System.Web.Mvc. Для ясности я предлагаю использовать полный путь.

Если вы используете Web API вместе с MVC, вам нужно будет реализовать два фильтра:

public class AuthorizeRedirectMVCAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        base.HandleUnauthorizedRequest(filterContext);

        if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new RedirectResult("~/Account/AccessDenied");
        }
    }
}

public class AuthorizeRedirectAPIAttribute : System.Web.Http.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        base.HandleUnauthorizedRequest(actionContext);

        if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
        }
    }
}

Обратите внимание, что asp.net позволит вам украсить ваш MVC-контроллер фильтром API - он просто не будет работать так, как вы ожидаете, поэтому сохраните свои имена атрибутов в явном виде.

Ответ 4

Если у вас есть контроллер и вы не хотите, чтобы в вашем коде был URL-адрес, вы также можете перенаправить его. Он не изменит URL в адресной строке браузера, поэтому пользователь никогда не увидит URL для неавторизованной страницы. Это было написано в MVC 3. Этот метод также будет работать, если вы хотите перенаправить их на страницу входа или если вы хотите перенаправить их на страницу, чтобы просто сказать им, что они не авторизованы. У меня был раздел в программе, на который у некоторых пользователей не было прав, но они вошли в систему, так что это то, что я использовал.

public class AuthorizedRedirect : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        bool isAuthorized = base.AuthorizeCore(httpContext);
        return isAuthorized;
    }
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    filterContext.RequestContext.RouteData.Values["controller"] = "error";
    filterContext.Result = new ViewResult { ViewName = "unauthorized" };
}

Ответ 5

И еще более простая версия, использующая настройки FormsAuthentication. Для тех, кто не знаком с контрактом, Contract.Requires - это дополнение .NET 4. Плюсы и минусы использования Кодовые контракты.

public class RequiresAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        Contract.Requires(filterContext != null);

        HttpContextBase context = filterContext.RequestContext.HttpContext;

        if (context.User.Identity.IsAuthenticated)
        {
            // user does not possess the required role permission
            string url = context.GetCustomErrorUrl(401);
            context.Response.Redirect(url);
        }
        else
        {

            // redirect the user to the login page
            string extraQueryString  = context.Request.RawUrl;
            FormsAuthentication.RedirectToLoginPage(extraQueryString);
        }
    }
}

Ответ 6

Переход от ответа divide_byzero, даже если у вас нет контроллера, вы все равно можете использовать HandleUnauthorizedRequest для изменения перенаправления.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class AuthoriseRedirect : AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.RequestContext.HttpContext.Response.Redirect("UrlToRedirectTo");
        }
    }

Пригодится, если у вас есть сайт устаревших веб-форм, который вы будете преобразовывать в MVC в течение более длительного периода времени.....!

Ответ 7

Мне нравится то, что опубликовано Brian Vander Plaats, добавлено несколько улучшений:

/// <summary>
/// Authorize or redirect to an unauthorized MVC action if the user does not have the required roles
/// (an unauthenticated user will be redirected to the defualt sign in action)
/// <para>Decorate an action or a controller like this [AuthorizeAndRedirect(Roles = "RoleName")]</para>
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AuthorizeOrRedirectAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        base.HandleUnauthorizedRequest(filterContext);

        if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            var routeData = new RouteData();
            routeData.Values.Add("controller", "Error");
            routeData.Values.Add("action", "Unauthorized");
            filterContext.Result = new RedirectToRouteResult(routeData.Values);
        }
    }
}

/// <summary>
/// Authorize or redirect to an unauthorized API action if the user does not have the required roles
/// (an unauthenticated user will be redirected to the defualt sign in action)
/// <para>Decorate an action or a controller like this [AuthorizeAndRedirect(Roles = "RoleName")]</para>
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AuthorizeOrRedirectApiFilterAttribute : System.Web.Http.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        base.HandleUnauthorizedRequest(actionContext);

        if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }
}