Как настроить ASP.NET Web API AuthorizeAttribute для необычных требований

Я наследую от System.Web.Http.AuthorizeAttribute, чтобы создать пользовательскую процедуру авторизации / аутентификации для удовлетворения некоторых необычных требований к веб-приложению, разработанному с использованием ASP.NET MVC 4. Это добавляет безопасность к веб-API, используемому для вызовов Ajax из Интернета клиент. Требования:

  1. Пользователь должен войти в систему каждый раз, когда выполняет транзакцию, чтобы проверить, что кто-то еще не подошел к рабочей станции после того, как кто-то вошел в систему и ушел.
  2. Роли не могут быть привязаны к методам веб-службы во время программы. Они должны быть назначены во время выполнения, чтобы администратор мог его настроить. Эта информация хранится в базе данных системы.

Веб-клиент представляет собой одностраничное приложение (SPA), поэтому типичная проверка подлинности не работает так хорошо, но я пытаюсь повторно использовать как можно большую часть инфраструктуры безопасности ASP.NET, чтобы соответствовать требованиям. Индивидуальный атрибут AuthorizeAttribute отлично подходит для требования 2 о том, какие роли связаны с методом веб-службы. Я принимаю три параметра, имя приложения, имя ресурса и операцию, чтобы определить, какие роли связаны с методом.

public class DoThisController : ApiController { [Authorize(Application = "MyApp", Resource = "DoThis", Operation = "read")] public string GetData() { return "We did this."; } } 

Я переопределяю метод OnAuthorization, чтобы получить роли и аутентифицировать пользователя. Поскольку пользователь должен быть аутентифицирован для каждой транзакции, я сокращаю назад и вперед болтовню, выполняя аутентификацию и авторизацию на том же шаге. Я получаю учетные данные пользователей от веб-клиента, используя базовую аутентификацию, которая передает зашифрованные учетные данные в HTTP-заголовке. Поэтому мой метод OnAuthorization выглядит следующим образом:

 public override void OnAuthorization(HttpActionContext actionContext) { string username; string password; if (GetUserNameAndPassword(actionContext, out username, out password)) { if (Membership.ValidateUser(username, password)) { FormsAuthentication.SetAuthCookie(username, false); base.Roles = GetResourceOperationRoles(); } else { FormsAuthentication.SignOut(); base.Roles = ""; } } else { FormsAuthentication.SignOut(); base.Roles = ""; } base.OnAuthorization(actionContext); } 

GetUserNameAndPassword извлекает учетные данные из заголовка HTTP. Затем я использую Memberhip.ValidateUser для проверки учетных данных. У меня есть пользовательский поставщик членства и поставщик роликов, подключенный к пользовательской базе данных. Если пользователь аутентифицирован, я получаю роли для ресурса и операции. Оттуда я использую базу OnAuthorization для завершения процесса авторизации. Вот где он ломается.

Если пользователь аутентифицирован, я использую методы проверки подлинности стандартных форм для входа пользователя в систему (FormsAuthentication.SetAuthCookie), и если они не сработают, я выхожу из них (FormsAuthentication.SignOut). Но проблема заключается в том, что базовый class OnAuthorization не имеет доступа к Principal, который обновляется, поэтому IsAuthenticated устанавливается в правильное значение. Это всегда один шаг назад. И я предполагаю, что он использует некоторое кэшированное значение, которое не обновляется до тех пор, пока не появится обратная связь с веб-клиентом.

Таким образом, все это приводит к моему конкретному вопросу, который есть, есть ли другой способ установить IsAuthenticated на правильное значение для текущего Принципала без использования файлов cookie? Мне кажется, что куки-файлы действительно не применяются в этом конкретном сценарии, где я должен аутентифицироваться каждый раз. Причина, по которой я знаю, IsAuthenticated не установлена ​​в правильное значение, я также переопределяю метод HandleUnauthorizedRequest :

  protected override void HandleUnauthorizedRequest(HttpActionContext filterContext) { if (((System.Web.HttpContext.Current.User).Identity).IsAuthenticated) { filterContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden); } else { base.HandleUnauthorizedRequest(filterContext); } } 

Это позволяет мне вернуть код статуса Forbidden для веб-клиента, если сбой произошел из-за авторизации вместо аутентификации, и он может ответить соответствующим образом.

Итак, каков правильный способ установки IsAuthenticated для текущего Принципа в этом сценарии?

Лучшее решение для моего сценария, похоже, полностью обходит базовую OnAuthorization . Поскольку я должен аутентифицироваться каждый раз, когда cookie и кеширование этого принципа не имеют большого значения. Итак, вот решение, которое я придумал:

 public override void OnAuthorization(HttpActionContext actionContext) { string username; string password; if (GetUserNameAndPassword(actionContext, out username, out password)) { if (Membership.ValidateUser(username, password)) { if (!isUserAuthorized(username)) actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden); } else { actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); } } else { actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest); } } 

Я разработал свой собственный метод для проверки ролей, называемых isUserAuthorized, и я больше не использую базовую OnAuthorization, так как он проверяет текущий Принцип, чтобы узнать, является ли он аутентифицированным . IsAuthenticated только позволяет получать, поэтому я не уверен, как еще это установить, и мне, похоже, не нужен текущий Принцип . Протестировал это, и он отлично работает.

Все еще интересно, есть ли у кого-то лучшее решение или можно увидеть какие-либо проблемы с этим.

Чтобы добавить к уже принятому ответу: проверяя текущий исходный код (aspnetwebstack.codeplex.com) для System.Web.Http.AuthorizeAttribute , похоже, что документация устарела. Base OnAuthorization() просто вызывает / проверяет private static SkipAuthorization() (который просто проверяет, используется ли AllowAnonymousAttribute в контексте, чтобы обойти остальную проверку проверки подлинности). Затем, если не пропустить, OnAuthorization() вызывает public IsAuthorized() и если этот вызов завершается с ошибкой, он вызывает защищенный виртуальный HandleUnauthorizedRequest() . И это все, что он делает …

 public override void OnAuthorization(HttpActionContext actionContext) { if (actionContext == null) { throw Error.ArgumentNull("actionContext"); } if (SkipAuthorization(actionContext)) { return; } if (!IsAuthorized(actionContext)) { HandleUnauthorizedRequest(actionContext); } } 

Заглядывая в IsAuthorized() , это где Принцип проверяется против ролей и пользователей. Таким образом, переопределение IsAuthorized() с тем, что у вас было выше, а не OnAuthorization() , – это путь. Опять же, вам все равно придется переопределить либо OnAuthorization() либо HandleUnauthorizedRequest() чтобы решить, когда вернуть ответ 401 против 403.

Чтобы добавить к абсолютно правильному ответу Кевина, я хотел бы сказать, что я могу немного изменить его, чтобы использовать существующий путь .NET framework для объекта ответа, чтобы обеспечить, чтобы нисходящий код в структуре (или других потребителях) не подвергался неблагоприятному воздействию по какой-то странной идиосинкразии, которая не может быть предсказана.

В частности, это означает использование этого кода:

 actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, REQUEST_NOT_AUTHORIZED); 

скорее, чем:

 actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); 

Где REQUEST_NOT_AUTHORIZED :

 private const string REQUEST_NOT_AUTHORIZED = "Authorization has been denied for this request."; 

Я вытащил эту string из SRResources.RequestNotAuthorized определения в .NET framework.

Отличный ответ Кевин! Я реализовал мой тот же самый путь, потому что выполнение OnAuthorization в базовом classе не имело смысла, потому что я OnAuthorization HTTP-заголовок, который был обычным для нашего приложения, и на самом деле не хотел вообще проверять Принципала, потому что его не было.

  • Как изменить заголовок заголовка Firefox для всех HTTP-запросов
  • Как безопасно хранить токен доступа и секрет в Android?
  • Откройте прямой файл на жестком диске из Firefox (файл: ///)
  • Каковы возможные проблемы безопасности с помощью SSH-демона?
  • Как отключить WebRTC в Google Chrome
  • Как отключить «Open File Security Warning», когда я хочу запустить загруженные исполняемые файлы?
  • Блокировка и завершение сеансов пользователей с помощью параметров групповой политики
  • разница между http.context.user и thread.currentprincipal и когда их использовать?
  • Безопасно ли использовать miredo-клиент?
  • Когда переходить от безопасности, управляемой контейнером, к альтернативам, таким как Apache Shiro, Spring Security?
  • Тип Keystore: какой из них использовать?
  • Interesting Posts
    Давайте будем гением компьютера.