Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Майк Уосон
Фильтр проверки подлинности — это компонент, который выполняет проверку подлинности HTTP-запроса. Веб-API 2 и MVC 5 поддерживают фильтры аутентификации, но они немного отличаются, в основном соглашениями именования интерфейсов фильтров. В этом разделе описаны фильтры проверки подлинности веб-API.
Фильтры проверки подлинности позволяют задать схему проверки подлинности для отдельных контроллеров или действий. Таким образом, приложение может поддерживать различные механизмы проверки подлинности для различных ресурсов HTTP.
В этой статье я покажу код из примера Basic Authentication на https://github.com/aspnet/samples. В примере показан фильтр проверки подлинности, реализующий схему проверки подлинности HTTP Basic Access (RFC 2617). Фильтр реализуется в классе с именем IdentityBasicAuthenticationAttribute. Я не показываю весь код из примера, а только части, иллюстрирующие запись фильтра проверки подлинности.
Настройка фильтра проверки подлинности
Как и другие фильтры, фильтры проверки подлинности можно применять для каждого контроллера, каждого действия или глобально ко всем контроллерам веб-API.
Чтобы применить фильтр проверки подлинности к контроллеру, украсите класс контроллера атрибутом фильтра. Следующий код задает [IdentityBasicAuthentication] фильтр для класса контроллера, который обеспечивает базовую проверку подлинности для всех действий контроллера.
[IdentityBasicAuthentication] // Enable Basic authentication for this controller.
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
public IHttpActionResult Get() { . . . }
public IHttpActionResult Post() { . . . }
}
Чтобы применить фильтр к одному действию, украсите действие фильтром. Следующий код задает [IdentityBasicAuthentication] фильтр для метода контроллера Post .
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
public IHttpActionResult Get() { . . . }
[IdentityBasicAuthentication] // Enable Basic authentication for this action.
public IHttpActionResult Post() { . . . }
}
Чтобы применить фильтр ко всем контроллерам веб-API, добавьте его в GlobalConfiguration.Filters.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new IdentityBasicAuthenticationAttribute());
// Other configuration code not shown...
}
}
Реализация фильтра проверки подлинности веб-API
В веб-API фильтры проверки подлинности реализуют интерфейс System.Web.Http.Filters.IAuthenticationFilter . Они также должны наследоваться от System.Attribute, чтобы применяться в качестве атрибутов.
Интерфейс IAuthenticationFilter имеет два метода:
- AuthenticateAsync выполняет проверку подлинности запроса, проверяя учетные данные в запросе, если они присутствуют.
- ChallengeAsync добавляет вызов проверки подлинности в ответ HTTP при необходимости.
Эти методы соответствуют потоку проверки подлинности, определенному в RFC 2612 и RFC 2617:
- Клиент отправляет учетные данные в заголовке авторизации. Обычно это происходит после того, как клиент получает от сервера ответ 401 (несанкционированный). Однако клиент может отправлять учетные данные с любым запросом, а не сразу после получения 401.
- Если сервер не принимает учетные данные, он возвращает ответ 401 (несанкционированный). Ответ содержит заголовок Www-Authenticate, содержащий одну или несколько проблем. Каждая проблема указывает схему проверки подлинности, распознаваемую сервером.
Сервер также может вернуть 401 из анонимного запроса. Обычно именно так инициируется процесс аутентификации.
- Клиент отправляет анонимный запрос.
- Сервер возвращает значение 401.
- Клиент повторно отправляет запрос с учетными данными.
Этот поток включает как шаги проверки подлинности , так и авторизации .
- Проверка подлинности подтверждает удостоверение клиента.
- Авторизация определяет, может ли клиент получить доступ к определенному ресурсу.
В веб-API фильтры проверки подлинности обрабатывают проверку подлинности, но не авторизацию. Авторизация должна выполняться фильтром авторизации или внутри действия контроллера.
Ниже приведен поток в конвейере веб-API 2:
- Перед вызовом действия веб-API создает список фильтров проверки подлинности для этого действия. К ним относятся фильтры с областью действия, областью контроллера и глобальной областью.
- Веб-API вызывает АутентификациюAsync для каждого фильтра в списке. Каждый фильтр может проверить учетные данные в запросе. Если любой фильтр успешно проверяет учетные данные, фильтр создает IPrincipal и присоединяет его к запросу. Фильтр также может вызвать ошибку на этом этапе. В таком случае остальная часть конвейера не выполняется.
- Если ошибка отсутствует, запрос проходит через остальную часть конвейера.
- Наконец, веб-API вызывает метод ChallengeAsync каждого фильтра проверки подлинности. Фильтры при необходимости используют этот метод для добавления дополнительной задачи в ответ. Обычно (но не всегда) это произойдет в ответ на ошибку 401.
На следующих схемах показаны два возможных случая. Во-первых, фильтр проверки подлинности успешно проходит проверку подлинности запроса, фильтр авторизации авторизует запрос, а действие контроллера возвращает 200 (ОК).
Во втором примере фильтр проверки подлинности проходит проверку подлинности запроса, но фильтр авторизации возвращает значение 401 (несанкционированно). В этом случае действие контроллера не вызывается. Фильтр проверки подлинности добавляет в ответ заголовок Www-Authenticate.
Другие сочетания возможны, например, если действие контроллера разрешает анонимные запросы, у вас может быть фильтр проверки подлинности, но авторизация отсутствует.
Реализация метода AuthenticateAsync
Метод AuthenticateAsync пытается пройти проверку подлинности запроса. Ниже приведена подпись метода:
Task AuthenticateAsync(
HttpAuthenticationContext context,
CancellationToken cancellationToken
)
Метод AuthenticateAsync должен выполнить одно из следующих действий:
- Ничего (no-op).
- Создайте IPrincipal и задайте его в запросе.
- Задайте результат ошибки.
Параметр (1) означает, что запрос не имеет учетных данных, которые фильтр понимает. Параметр (2) означает, что фильтр успешно прошел проверку подлинности запроса. Вариант (3) означает, что запрос имел недопустимые учетные данные (например, неправильный пароль), что вызывает ответ с ошибкой.
Ниже приведен общий план реализации AuthenticateAsync.
- Найдите учетные данные в запросе.
- Если учетные данные отсутствуют, ничего не делать и возвращать (no-op).
- Если есть учетные данные, но фильтр не распознает схему проверки подлинности, ничего не делайте и возвращайте (no-op). Другой фильтр в конвейере может понять схему.
- Если есть учетные данные, которые понимает фильтр, попробуйте выполнить проверку подлинности.
- Если учетные данные недопустимы, верните 401, установив
context.ErrorResult. - Если учетные данные допустимы, создайте IPrincipal и установите
context.Principal.
В следующем коде показан метод AuthenticationAsync из примера базовой проверки подлинности . Примечания указывают на каждый шаг. В коде показано несколько типов ошибок: заголовок авторизации без учетных данных, неправильных учетных данных и недопустимых имени пользователя или пароля.
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
// 1. Look for credentials in the request.
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
// 2. If there are no credentials, do nothing.
if (authorization == null)
{
return;
}
// 3. If there are credentials but the filter does not recognize the
// authentication scheme, do nothing.
if (authorization.Scheme != "Basic")
{
return;
}
// 4. If there are credentials that the filter understands, try to validate them.
// 5. If the credentials are bad, set the error result.
if (String.IsNullOrEmpty(authorization.Parameter))
{
context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
return;
}
Tuple<string, string> userNameAndPassword = ExtractUserNameAndPassword(authorization.Parameter);
if (userNameAndPassword == null)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
}
string userName = userNameAndPassword.Item1;
string password = userNameAndPassword.Item2;
IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);
if (principal == null)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
}
// 6. If the credentials are valid, set principal.
else
{
context.Principal = principal;
}
}
Настройка результата ошибки
Если учетные данные недопустимы, фильтр должен задать context.ErrorResult значение IHttpActionResult , которое создает ответ на ошибку. Дополнительные сведения об IHttpActionResult см. в разделе "Результаты действий" в веб-API 2.
Пример базовой проверки подлинности включает класс AuthenticationFailureResult, подходящий для этой цели.
public class AuthenticationFailureResult : IHttpActionResult
{
public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request)
{
ReasonPhrase = reasonPhrase;
Request = request;
}
public string ReasonPhrase { get; private set; }
public HttpRequestMessage Request { get; private set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
private HttpResponseMessage Execute()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.RequestMessage = Request;
response.ReasonPhrase = ReasonPhrase;
return response;
}
}
Реализация ChallengeAsync
Целью метода ChallengeAsync является добавление вызовов проверки подлинности в ответ при необходимости. Ниже приведена подпись метода:
Task ChallengeAsync(
HttpAuthenticationChallengeContext context,
CancellationToken cancellationToken
)
Метод вызывается для каждого фильтра проверки подлинности в конвейере запросов.
Важно понимать, что ChallengeAsync вызывается перед созданием HTTP-ответа и, возможно, даже до запуска действия контроллера. При вызове ChallengeAsynccontext.Result содержит IHttpActionResult, который используется позже для создания HTTP-ответа. Поэтому при вызове ChallengeAsync вы еще не знаете ничего о HTTP-ответе. Метод ChallengeAsync должен заменить исходное значение context.Result новым IHttpActionResult. Этот объект IHttpActionResult должен обернуть исходный context.Resultобъект.
Я буду называть исходный результат IHttpActionResultвнутренним результатом, а новый IHttpActionResult— внешним результатом. Внешний результат должен выполнять следующие действия:
- Вызов внутреннего результата для создания HTTP-ответа.
- Проверьте ответ.
- При необходимости добавьте вызов проверки подлинности в ответ.
Следующий пример взят из примера базовой проверки подлинности. Он определяет IHttpActionResult для внешнего результата.
public class AddChallengeOnUnauthorizedResult : IHttpActionResult
{
public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
{
Challenge = challenge;
InnerResult = innerResult;
}
public AuthenticationHeaderValue Challenge { get; private set; }
public IHttpActionResult InnerResult { get; private set; }
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = await InnerResult.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
// Only add one challenge per authentication scheme.
if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
{
response.Headers.WwwAuthenticate.Add(Challenge);
}
}
return response;
}
}
Свойство InnerResult содержит внутреннее свойство IHttpActionResult. Свойство Challenge представляет заголовок Www-Authentication. Обратите внимание, что ExecuteAsync сначала вызывает InnerResult.ExecuteAsync для создания HTTP-ответа, а затем добавляет вызов проверки при необходимости.
Проверьте код ответа перед добавлением задачи. Большинство схем проверки подлинности добавляют только вызов, если ответ равен 401, как показано здесь. Однако некоторые схемы проверки подлинности добавляют вызов в ответ на успешное выполнение. Например, см. раздел "Согласование " (RFC 4559).
Учитывая класс, фактический AddChallengeOnUnauthorizedResult код в ChallengeAsync прост. Вы просто создаёте результат и прикрепляете его к context.Result.
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var challenge = new AuthenticationHeaderValue("Basic");
context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
return Task.FromResult(0);
}
Примечание. Пример базовой проверки подлинности абстрагирует эту логику немного, поместив его в метод расширения.
Объединение фильтров проверки подлинности с аутентификацией на уровне хоста
Проверка подлинности на уровне узла выполняется узлом (например, IIS), прежде чем запрос достигнет платформы веб-API.
Часто может потребоваться включить проверку подлинности на уровне узла для остальной части приложения, но отключить его для контроллеров веб-API. Например, типичным сценарием является включение аутентификации с использованием форм на уровне узла, но использование аутентификации на основе токенов для Web API.
Чтобы отключить проверку подлинности на уровне узла в конвейере веб-API, вызовите config.SuppressHostPrincipal() в конфигурации. Это приводит к удалению IPrincipal из любого запроса, который проходит через конвейер веб-API. Фактически он "отменяет проверку подлинности" запроса.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.SuppressHostPrincipal();
// Other configuration code not shown...
}
}
Дополнительные ресурсы
фильтры безопасности веб-API ASP.NET (журнал MSDN)