Почему AuthorizeAttribute перенаправляет на страницу входа в систему при сбое аутентификации и авторизации? - PullRequest
255 голосов
/ 26 октября 2008

В ASP.NET MVC вы можете пометить метод контроллера с помощью AuthorizeAttribute, например:

[Authorize(Roles = "CanDeleteTags")]
public void Delete(string tagName)
{
    // ...
}

Это означает, что, если зарегистрированный в данный момент пользователь не находится в роли «CanDeleteTags», метод контроллера никогда не будет вызываться.

К сожалению, для сбоев AuthorizeAttribute возвращает HttpUnauthorizedResult, что всегда возвращает код состояния HTTP 401. Это вызывает перенаправление на страницу входа.

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

Похоже, что AuthorizeAttribute объединяет аутентификацию и авторизацию.

Это похоже на упущение в ASP.NET MVC или я что-то упустил?

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

Конечно, мне не нужно было это делать?

Ответы [ 7 ]

296 голосов
/ 01 мая 2011

Когда он был впервые разработан, System.Web.Mvc.AuthorizeAttribute делал правильные вещи - В более старых версиях спецификации HTTP использовался код состояния 401 как для «неавторизованного», так и для «неавторизованного».

Из оригинальной спецификации:

Если в запрос уже включены учетные данные авторизации, то в ответе 401 указывается, что для этих учетных данных было отказано в авторизации.

На самом деле, вы можете увидеть путаницу прямо здесь - она ​​использует слово «авторизация», когда означает «аутентификация». Однако в повседневной практике имеет смысл возвращать 403 Запрещено, когда пользователь аутентифицирован, но не авторизован. Маловероятно, что у пользователя будет второй набор учетных данных, который предоставит ему доступ - плохой пользовательский опыт со всех сторон.

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

К счастью, спецификации HTTP были обновлены (июнь 2014 г.), чтобы устранить неоднозначность.

Из «Гипертекстового транспортного протокола (HTTP / 1.1): аутентификация» (RFC 7235):

Код состояния 401 (неавторизованный) указывает, что запрос не был применен, поскольку в нем отсутствуют действительные учетные данные для аутентификации целевого ресурса.

Из «Протокола передачи гипертекста (HTTP / 1.1): семантика и контент» (RFC 7231):

Код состояния 403 (запрещено) указывает, что сервер понял запрос, но отказывается его авторизовать.

Интересно, что во время выпуска ASP.NET MVC 1 поведение AuthorizeAttribute было правильным. Теперь поведение некорректно - спецификация HTTP / 1.1 была исправлена.

Вместо того, чтобы пытаться изменить перенаправления страницы входа в ASP.NET, проще просто устранить проблему у источника. Вы можете создать новый атрибут с тем же именем (AuthorizeAttribute) в пространстве имен вашего сайта по умолчанию (это очень важно), после чего компилятор автоматически подберет его вместо стандартного MVC. Конечно, вы всегда можете дать атрибуту новое имя, если предпочитаете такой подход.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}
24 голосов
/ 01 апреля 2009

Добавьте это к вашей функции входа в систему Page_Load:

// User was redirected here because of authorization section
if (User.Identity != null && User.Identity.IsAuthenticated)
    Response.Redirect("Unauthorized.aspx");

Когда пользователь перенаправлен туда, но уже вошел в систему, отображается неавторизованная страница. Если они не вошли в систему, он провалится и покажет страницу входа.

4 голосов
/ 27 октября 2008

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

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

4 голосов
/ 27 октября 2008

К сожалению, вы имеете дело с поведением по умолчанию проверки подлинности форм ASP.NET. Здесь есть обходной путь (я не пробовал):

http://www.codeproject.com/KB/aspnet/Custon401Page.aspx

(Это не относится к MVC)

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

Вероятно, было бы неплохо иметь дополнительный параметр в атрибуте, чтобы указать, куда перенаправлять неавторизованного пользователя. Но пока я смотрю на AuthorizeAttribute как на сеть безопасности.

0 голосов
/ 18 марта 2019

В моем случае проблема заключалась в том, что «спецификация HTTP использовала код состояния 401 как для« неавторизованного », так и для« неавторизованного »». Как сказал ShadowChaser.

Это решение работает для меня:

if (User != null &&  User.Identity.IsAuthenticated && Response.StatusCode == 401)
{
    //Do whatever

    //In my case redirect to error page
    Response.RedirectToRoute("Default", new { controller = "Home", action = "ErrorUnauthorized" });
}
0 голосов
/ 15 февраля 2018

Если вы используете aspnetcore 2.0, используйте это:

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Core
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class AuthorizeApiAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var user = context.HttpContext.User;

            if (!user.Identity.IsAuthenticated)
            {
                context.Result = new UnauthorizedResult();
                return;
            }
        }
    }
}
0 голосов
/ 09 января 2015

Попробуйте это в обработчике Application_EndRequest вашего файла Global.ascx

if (HttpContext.Current.Response.Status.StartsWith("302") && HttpContext.Current.Request.Url.ToString().Contains("/<restricted_path>/"))
{
    HttpContext.Current.Response.ClearContent();
    Response.Redirect("~/AccessDenied.aspx");
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...