Почему AuthorizeAttribute перенаправляет на страницу входа для проверки подлинности и авторизации?

В 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. В настоящее время это перенаправляется на страницу с ошибкой.

Неужели мне это не нужно было делать?

Ответ 1

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

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

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

Фактически, вы можете видеть путаницу прямо там - он использует слово "авторизация", когда это означает "аутентификация". Однако в повседневной практике имеет смысл вернуть 403 Запрещено, когда пользователь аутентифицирован, но не авторизован. Вряд ли у пользователя будет второй набор учетных данных, который предоставит им доступ - плохая работа со всеми пользователями.

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

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

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

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

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

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

Интересно, что в момент выхода 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);
        }
    }
}

Ответ 2

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

Ответ 5

Попробуйте это в своем обработчике 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");
}