Статья об обеспечении безопасности веб-интерфейсов API с помощью отдельной учетной записи и локального имени входа в веб-API ASP.NET 2.2
Майк Уосон
В этом разделе показано, как защитить веб-API с помощью OAuth2 для проверки подлинности в базе данных членства.
Версии программного обеспечения, используемые в руководстве
В Visual Studio 2013 шаблон проекта веб-API предоставляет три варианта проверки подлинности:
- Индивидуальные учетные записи. Приложение использует базу данных членства.
- Учетные записи организации. Пользователи войдите с помощью учетных данных Azure Active Directory, Office 365 или локальных учетных данных Active Directory.
- Проверка подлинности Windows. Этот параметр предназначен для приложений интрасети и использует модуль IIS проверки подлинности Windows.
Отдельные учетные записи предоставляют два способа входа пользователя:
- Локальное имя входа. Пользователь регистрируется на сайте, введя имя пользователя и пароль. Приложение сохраняет хэш паролей в базе данных членства. При входе пользователя система удостоверений ASP.NET проверяет пароль.
- Социальное имя входа. Пользователь входит в систему с помощью внешней службы, например Facebook, Майкрософт или Google. Приложение по-прежнему создает запись для пользователя в базе данных членства, но не сохраняет учетные данные. Пользователь проходит проверку подлинности, выполнив вход во внешнюю службу.
В этой статье рассматривается сценарий локального входа. Для локального и социального входа веб-API использует OAuth2 для проверки подлинности запросов. Однако потоки учетных данных отличаются для локального и социального входа.
В этой статье мы прокажем простое приложение, которое позволяет пользователю входить в систему и отправлять прошедшие проверку подлинности вызовы AJAX в веб-API. Пример кода можно скачать здесь. В readme описывается создание примера с нуля в Visual Studio.
Пример приложения использует Knockout.js для привязки данных и jQuery для отправки запросов AJAX. Я буду сосредоточиться на вызовах AJAX, поэтому вам не нужно знать Knockout.js для этой статьи.
На этом пути я охарактеризую:
- Что делает приложение на стороне клиента.
- Что происходит на сервере.
- HTTP-трафик в середине.
Сначала необходимо определить некоторые терминологии OAuth2.
- Ресурс. Некоторые фрагменты данных, которые можно защитить.
- Сервер ресурсов. Сервер, на котором размещен ресурс.
- Владелец ресурса. Сущность, которая может предоставить разрешение на доступ к ресурсу. (Как правило, пользователь.)
- Клиент: приложение, которое хочет получить доступ к ресурсу. В этой статье клиент является веб-браузером.
- Маркер доступа. Маркер, предоставляющий доступ к ресурсу.
- Маркер носителя. Определенный тип маркера доступа с свойством, которое любой пользователь может использовать маркер. Другими словами, клиенту не нужен криптографический ключ или другой секрет для использования маркера носителя. По этой причине маркеры носителя должны использоваться только по протоколу HTTPS и должны иметь относительно короткое время истечения срока действия.
- Сервер авторизации. Сервер, предоставляющий маркеры доступа.
Приложение может выступать как в качестве сервера авторизации, так и сервера ресурсов. Шаблон проекта веб-API следует этому шаблону.
Локальный поток учетных данных для входа
Для локального входа веб-API использует поток паролей владельца ресурса, определенный в OAuth2.
- Пользователь вводит имя и пароль в клиент.
- Клиент отправляет эти учетные данные серверу авторизации.
- Сервер авторизации выполняет проверку подлинности учетных данных и возвращает маркер доступа.
- Чтобы получить доступ к защищенному ресурсу, клиент включает маркер доступа в заголовок авторизации HTTP-запроса.
При выборе отдельных учетных записей в шаблоне проекта веб-API проект включает сервер авторизации, который проверяет учетные данные пользователя и выдает маркеры. На следующей схеме показан тот же поток учетных данных с точки зрения компонентов веб-API.
В этом сценарии контроллеры веб-API действуют как серверы ресурсов. Фильтр проверки подлинности проверяет маркеры доступа, а атрибут [Авторизовать] используется для защиты ресурса. Если контроллер или действие имеют атрибут [Авторизовать] , все запросы к контроллеру или действию должны проходить проверку подлинности. В противном случае авторизация запрещена, а веб-API возвращает ошибку 401 (несанкционированный).
Сервер авторизации и фильтр проверки подлинности вызываются в компонент ПО промежуточного слоя OWIN, который обрабатывает сведения OAuth2. Далее в этом руководстве я охарактеризую дизайн.
Отправка несанкционированного запроса
Чтобы приступить к работе, запустите приложение и нажмите кнопку "Вызов API ". По завершении запроса в поле "Результат" появится сообщение об ошибке. Это связано с тем, что запрос не содержит маркер доступа, поэтому запрос не несанкционированен.
Кнопка API вызова отправляет запрос AJAX в ~/api/values, который вызывает действие контроллера веб-API. Ниже приведен раздел кода JavaScript, который отправляет запрос AJAX. В примере приложения весь код приложения JavaScript находится в файле Scripts\app.js.
// If we already have a bearer token, set the Authorization header.
var token = sessionStorage.getItem(tokenKey);
var headers = {};
if (token) {
headers.Authorization = 'Bearer ' + token;
}
$.ajax({
type: 'GET',
url: 'api/values/1',
headers: headers
}).done(function (data) {
self.result(data);
}).fail(showError);
Пока пользователь не входит в систему, маркер носителя отсутствует и поэтому в запросе нет заголовка авторизации. Это приводит к возврату ошибки 401.
Ниже приведен HTTP-запрос. (Я использовал Fiddler для записи HTTP-трафика.)
GET https://localhost:44305/api/values HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Accept-Language: en-US,en;q=0.5
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
Отклик HTTP:
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
WWW-Authenticate: Bearer
Date: Tue, 30 Sep 2014 21:54:43 GMT
Content-Length: 61
{"Message":"Authorization has been denied for this request."}
Обратите внимание, что ответ содержит заголовок Www-Authenticate с заданным для носителя вызовом. Это означает, что сервер ожидает маркер носителя.
Регистрация пользователя
В разделе "Регистрация" приложения введите сообщение электронной почты и пароль и нажмите кнопку "Зарегистрировать".
Вам не нужно использовать допустимый адрес электронной почты для этого примера, но реальное приложение подтвердит адрес. (См. раздел Создайте безопасное веб-приложение ASP.NET MVC 5 с помощью входа, подтверждения электронной почты и сброса пароля.) Для пароля используйте примерно такую букву, как Password1!", с буквой верхнего регистра, строчным регистром, числом и не альфа-числовым символом. Чтобы обеспечить простое приложение, я оставил проверку на стороне клиента, поэтому если возникла проблема с форматом пароля, вы получите ошибку 400 (неправильный запрос).
Кнопка "Регистрация " отправляет запрос POST в ~/api/Account/Register/. Текст запроса — это объект JSON, содержащий имя и пароль. Ниже приведен код JavaScript, который отправляет запрос:
var data = {
Email: self.registerEmail(),
Password: self.registerPassword(),
ConfirmPassword: self.registerPassword2()
};
$.ajax({
type: 'POST',
url: '/api/Account/Register',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data)
}).done(function (data) {
self.result("Done!");
}).fail(showError);
HTTP-запрос, где $CREDENTIAL_PLACEHOLDER$
является заполнителем для пары "ключ-значение пароля":
POST https://localhost:44305/api/Account/Register HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Content-Type: application/json; charset=utf-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
Content-Length: 84
{"Email":"alice@example.com",$CREDENTIAL_PLACEHOLDER1$,$CREDENTIAL_PLACEHOLDER2$"}
Отклик HTTP:
HTTP/1.1 200 OK
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 00:57:58 GMT
Content-Length: 0
Этот запрос обрабатывается классом AccountController
. AccountController
Внутренне использует ASP.NET Identity для управления базой данных членства.
При локальном запуске приложения из Visual Studio учетные записи пользователей хранятся в LocalDB в таблице AspNetUsers. Чтобы просмотреть таблицы в Visual Studio, щелкните меню "Вид", выберите обозреватель серверов, а затем разверните узел "Подключения к данным".
Получение маркера доступа
До сих пор мы не сделали никаких OAuth, но теперь мы увидим сервер авторизации OAuth в действии, когда мы запрашиваем маркер доступа. В области входа в пример приложения введите сообщение электронной почты и пароль и нажмите кнопку "Войти".
Кнопка "Вход в систему" отправляет запрос в конечную точку маркера. Текст запроса содержит следующие данные в кодировке form-url:
- grant_type: "пароль"
- имя пользователя: <электронная почта пользователя>
- пароль: <пароль>
Ниже приведен код JavaScript, который отправляет запрос AJAX:
var loginData = {
grant_type: 'password',
username: self.loginEmail(),
password: self.loginPassword()
};
$.ajax({
type: 'POST',
url: '/Token',
data: loginData
}).done(function (data) {
self.user(data.userName);
// Cache the access token in session storage.
sessionStorage.setItem(tokenKey, data.access_token);
}).fail(showError);
Если запрос выполнен успешно, сервер авторизации возвращает маркер доступа в тексте ответа. Обратите внимание, что маркер хранится в хранилище сеансов, который будет использоваться позже при отправке запросов в API. В отличие от некоторых форм проверки подлинности (таких как проверка подлинности на основе файлов cookie), браузер не будет автоматически включать маркер доступа в последующие запросы. Приложение должно сделать это явным образом. Это хорошо, потому что это ограничивает уязвимости CSRF.
Запрос HTTP:
POST https://localhost:44305/Token HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
Content-Length: 68
grant_type=password&username=alice%40example.com&password=Password1!
Вы увидите, что запрос содержит учетные данные пользователя. Для обеспечения безопасности уровня транспорта необходимо использовать ПРОТОКОЛ HTTPS.
Отклик HTTP:
HTTP/1.1 200 OK
Content-Length: 669
Content-Type: application/json;charset=UTF-8
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 01:22:36 GMT
{
"access_token":"imSXTs2OqSrGWzsFQhIXziFCO3rF...",
"token_type":"bearer",
"expires_in":1209599,
"userName":"alice@example.com",
".issued":"Wed, 01 Oct 2014 01:22:33 GMT",
".expires":"Wed, 15 Oct 2014 01:22:33 GMT"
}
Для удобства чтения я отступил JSON и усекал маркер доступа, который является довольно длинным.
token_type
Свойства access_token
и expires_in
свойства определяются спецификацией OAuth2. Другие свойства (userName
, .issued
и.expires
) предназначены только для информационных целей. Код, добавляющий эти дополнительные свойства в метод, можно найти в TokenEndpoint
файле /Providers/ApplicationOAuthProvider.cs.
Отправка аутентифицированного запроса
Теперь, когда у нас есть маркер носителя, мы можем выполнить прошедший проверку подлинности запрос к API. Это делается путем задания заголовка авторизации в запросе. Нажмите кнопку API вызова еще раз, чтобы увидеть это.
Запрос HTTP:
GET https://localhost:44305/api/values/1 HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Authorization: Bearer imSXTs2OqSrGWzsFQhIXziFCO3rF...
X-Requested-With: XMLHttpRequest
Отклик HTTP:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 01:41:29 GMT
Content-Length: 27
"Hello, alice@example.com."
Выйти
Так как браузер не кэширует учетные данные или маркер доступа, выход из системы просто является вопросом "забыть" маркер, удалив его из хранилища сеансов:
self.logout = function () {
sessionStorage.removeItem(tokenKey)
}
Общие сведения о шаблоне проекта отдельных учетных записей
При выборе отдельных учетных записей в шаблоне проекта веб-приложения ASP.NET проект включает:
- Сервер авторизации OAuth2.
- Конечная точка веб-API для управления учетными записями пользователей
- Модель EF для хранения учетных записей пользователей.
Ниже приведены основные классы приложений, реализующие следующие функции:
AccountController
. Предоставляет конечную точку веб-API для управления учетными записями пользователей. ЭтоRegister
единственное действие, которое мы использовали в этом руководстве. Другие методы в классе поддерживают сброс пароля, учетные записи социальных параметров и другие функции.ApplicationUser
, определенный в /Models/IdentityModels.cs. Этот класс представляет собой модель EF для учетных записей пользователей в базе данных членства.ApplicationUserManager
, определенный в /App_Start/IdentityConfig.cs Этот класс является производным от UserManager и выполняет операции с учетными записями пользователей, например создание нового пользователя, проверка паролей и т. д. и т. д. И автоматически сохраняет изменения в базе данных.ApplicationOAuthProvider
. Этот объект подключается к ПО промежуточного слоя OWIN и обрабатывает события, вызванные ПО промежуточного слоя. Он является производным от OAuthAuthorizationServerProvider.
Настройка сервера авторизации
В StartupAuth.cs следующий код настраивает сервер авторизации OAuth2.
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// Note: Remove the following line before you deploy to production:
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
Свойство TokenEndpointPath
— это URL-путь к конечной точке сервера авторизации. Это URL-адрес, который приложение использует для получения маркеров носителя.
Свойство Provider
указывает поставщика, подключающегося к ПО промежуточного слоя OWIN, и обрабатывает события, вызванные ПО промежуточного слоя.
Ниже приведен базовый поток, когда приложение хочет получить маркер:
- Чтобы получить маркер доступа, приложение отправляет запрос на ~/Token.
- ПО промежуточного слоя OAuth вызывается
GrantResourceOwnerCredentials
поставщиком. - Поставщик вызывает
ApplicationUserManager
проверку учетных данных и создание удостоверения утверждений. - При успешном завершении поставщик создает билет проверки подлинности, который используется для создания маркера.
По промежуточному слоям OAuth ничего не известно о учетных записях пользователей. Поставщик взаимодействует между ПО промежуточного слоя и ASP.NET Identity. Дополнительные сведения о реализации сервера авторизации см. в разделе OWIN OAuth 2.0 Authorization Server.
Настройка веб-API для использования маркеров носителя
В методе WebApiConfig.Register
следующий код настраивает проверку подлинности для конвейера веб-API:
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
Класс HostAuthenticationFilter обеспечивает проверку подлинности с помощью маркеров носителя.
Метод SuppressDefaultHostAuthentication сообщает веб-API игнорировать любую проверку подлинности, которая происходит до того, как запрос достигнет конвейера веб-API, по службам IIS или по промежуточному по промежуточному слоям OWIN. Таким образом можно ограничить веб-API для проверки подлинности только с помощью токенов носителя.
Примечание.
В частности, часть MVC приложения может использовать проверку подлинности форм, которая хранит учетные данные в файле cookie. Проверка подлинности на основе файлов cookie требует использования маркеров защиты от подделки, чтобы предотвратить атаки CSRF. Это проблема для веб-API, так как для веб-API нет удобного способа отправки маркера защиты от подделки клиенту. (Дополнительные сведения об этой проблеме см. в разделе Предотвращение атак CSRF в веб-API.) Вызов SuppressDefaultHostAuthentication гарантирует, что веб-API не уязвим для атак CSRF с учетными данными, хранящимися в файлах cookie.
Когда клиент запрашивает защищенный ресурс, вот что происходит в конвейере веб-API:
- Фильтр HostAuthentication вызывает ПО промежуточного слоя OAuth для проверки маркера.
- ПО промежуточного слоя преобразует маркер в удостоверение утверждений.
- На этом этапе запрос проходит проверку подлинности , но не авторизован.
- Фильтр авторизации проверяет удостоверение утверждений. Если утверждения авторизуют пользователя для этого ресурса, запрос авторизован. По умолчанию атрибут [Авторизовать] будет авторизовать любой запрос, прошедший проверку подлинности. Однако вы можете авторизовать по роли или другим утверждениям. Дополнительные сведения см. в разделе "Проверка подлинности и авторизация" в веб-API.
- Если предыдущие шаги выполнены успешно, контроллер возвращает защищенный ресурс. В противном случае клиент получает ошибку 401 (несанкционированный).
Дополнительные ресурсы
- удостоверение ASP.NET
- Общие сведения о функциях безопасности в шаблоне SPA для RC VS2013. Запись блога MSDN по Hongye Sun.
- Рассекая шаблон отдельных учетных записей веб-API, часть 2. Локальные учетные записи. Запись блога Доминик Байер.
- Проверка подлинности узла и веб-API с помощью OWIN. Хорошее объяснение
SuppressDefaultHostAuthentication
иHostAuthenticationFilter
Брок Аллен. - Настройка сведений о профиле в ASP.NET identity в шаблонах VS 2013. Запись блога MSDN pranav Rastogi.
- Управление временем существования запроса для класса UserManager в ASP.NET Identity. Запись блога MSDN Сухас Джоши с хорошим объяснением
UserManager
класса.