Что такое учетные данные пароля владельца ресурса? Как я могу защитить свой клиент Angular с помощью токенов-носителей OAuth и JWT? В этом посте я сосредоточусь на предоставлении учетных данных пароля владельца ресурса, другом типе потока учетных данных, поддерживаемом протоколом OAuth, и на том, как его можно использовать для защиты определенных ресурсов в приложении Angular. Как и в предыдущем посте, я создам сервер авторизации с нуля, затем сервер ресурсов, простой ASP.NET Core RESTful API и, наконец, приложение Angular 6 со всеми элементами, необходимыми для предотвращения несанкционированного доступа.

Исходный код

Код, изложенный в этой статье, можно найти в моем репозитории на GitHub.

Потоки OAuth (снова)

В предыдущем посте я кратко представил доступные потоки OAuth, уделив больше внимания предоставлению авторизации учетных данных клиента. Чтобы освежить нашу память, предоставление авторизации или поток OAuth — это учетные данные, представляющие авторизацию владельца ресурса с помощью токена доступа, при этом OAuth поддерживает 4 различных потока, но также позволяет расширить текущую спецификацию с помощью пользовательских потоков.

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

Поток учетных данных владельца ресурса

Существует два типа потоков учетных данных: один — учетные данные клиента, а другой — учетные данные пароля владельца ресурса (или ROPC). Используя поток ROPC, учетные данные (то есть имя пользователя и пароль) владельца ресурса (то есть пользователя) можно обменять на токен доступа в одном запросе. Этот тип гранта следует использовать только тогда, когда существует высокая степень доверия между владельцем ресурса и клиентом, а другие гранты авторизации не могут быть применены. Протокол OAuth стал очень популярным, потому что его цель — устранить угрозу безопасности, связанную с обменом учетными данными пользователя с клиентскими приложениями, особенно сторонними, но этот поток, похоже, противоречит утверждениям протокола.

Согласно RFC-6749, сторонним приложениям никогда не должно быть позволено использовать этот грант, так как он нарушает цели протокола OAuth, давая критическую информацию ненадежным клиентским приложениям. Итак, когда следует использовать? Обычно этот тип гранта используется в сценариях, где клиентское приложение является частью текущей системы, поэтому ему разрешено знать некоторую информацию о владельце ресурса.

Другим вариантом использования может быть миграция существующих клиентов, использующих схемы прямой проверки подлинности, такие как HTTP Basic или Digest, на OAuth, поскольку шаги, необходимые для проверки подлинности, очень похожи в этих сценариях. Это верно, так как этот тип гранта на самом деле напоминает схемы прямой аутентификации, упомянутые выше, поэтому переход на OAuth не должен быть большой проблемой для таких систем.

Наконец, рассмотрите возможность использования этого типа гранта, когда ничего другого не доступно, и в этом случае всегда используйте HTTP поверх безопасности транспортного уровня (TLS), чтобы можно было снизить риски, такие как атаки «человек посередине». Помните, что при использовании этого типа гранта имя пользователя и пароль включаются в запрос, поэтому в случае незащищенного HTTP-сценария злоумышленник может легко украсть учетные данные пользователя.

На следующем рисунке показано предоставление пароля и танец OAuth между объектами, участвующими в этом потоке.

Запрос авторизации

Запрос маркера должен быть в определенном формате при использовании потока ROPC и должен содержать следующие параметры в теле запроса. Обратите внимание, что запрос имеет формат POST, а тело должно быть в формате x-www-form-urlencoded. Обязательны все, кроме параметра scope.

  • имя пользователя. Имя пользователя владельца ресурса.
  • пароль. Пароль владельца ресурса.
  • grant_type. Различает используемый тип гранта. Для ROPC это пароль.
  • область действия (необязательно). Определяет область запроса авторизации.
  • идентификатор_клиента. Выданный идентификатор клиента.
  • секрет_клиента. Выданный секрет клиента.

Последние два (client_id и client_secret) следует использовать только тогда, когда клиент защищен и выдал секрет сервером авторизации. Когда это так, эти два также необходимы.

Давайте начнем строить

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

Затем я перейду к серверу ресурсов, который будет представлять собой простой веб-API ASP.NET Core с одним контроллером с именем PetsController и одним действием GET, которое возвращает набор имен домашних животных.

Наконец, я собираюсь настроить клиент Angular 6 с помощью Angular CLI, настроить необходимые компоненты, сервисы и маршруты, защитить определенные маршруты с помощью защиты маршрутов, создать страницу входа, где пользователь может ввести свои учетные данные и перехватчики HTTP, чтобы включить токен доступа. для каждого аутентифицированного запроса. В результате пользователь сможет перейти к маршруту домашних животных в клиенте Angular и просмотреть список имен домашних животных, полученный из RESTful API.

Сервер авторизации

Для сервера авторизации я снова буду использовать NuGet-пакет IdentityServer4, который значительно упрощает такие сценарии пользовательских поставщиков удостоверений.

Я использую dotnet CLI для установки пакета IdentityServer4 в приложение сервера авторизации, которое представляет собой пустое веб-приложение ASP.NET Core.

Настройка довольно проста и очень похожа на ту, что была представлена ​​в предыдущем посте. Подводя итог, мне нужно будет настроить учетные данные для подписи, поэтому для этого простого примера я буду использовать учетные данные для подписи разработчика, которые предоставляет IdentityServer4. Мне также понадобится ресурс API, клиент для сопоставления с этим API и пользователь с именем пользователя и паролем. , который будет использоваться в потоке ROPC. Ниже приведена эта настройка, переведенная из требований в код.

Я добавил тестового пользователя, используя класс TestUser, включенный в структуру IdentityServer4, с некоторыми очень тривиальными учетными данными имени пользователя и пароля. SubjectId — это идентификатор имени пользователя, и я также включил имя и адрес электронной почты для того же пользователя.

Ресурс API также тривиален и следует той же схеме, что и в прошлый раз. В качестве второго параметра я передал все типы утверждений, которые должны быть включены в токен JWT. В этом случае этот API позволяет перечислять утверждения Name и Email в маркере JWT. Эти два определения определены в претензии TestUser.

Клиент кажется немного другим, чем в прошлый раз. Одним из отличий является AllowedGrantTypes, который теперь использует тип гранта ResourceOwnerPassword. Новым также является свойство AllowedCorsOrigins, которое устанавливает разрешенные источники для клиентов JavaScript, при этом источник, указанный здесь, является URL-адресом клиента Angular, который я вскоре продемонстрирую.

Наконец, в методе Configure мне нужно включить IdentityServer в конвейер ASP.NET Core.

Когда сервер авторизации установлен, пришло время настроить сервер ресурсов и предоставить конечную точку /pets.

Сервер ресурсов

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

Наконец, мне нужно настроить промежуточное ПО аутентификации для использования токенов-носителей JWT точно так же, как я сделал в предыдущем посте для предоставления учетных данных клиента.

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

Наконец, в методе Configure мне нужно будет зарегистрировать конфигурацию аутентификации, а также включить CORS, поскольку доступ к этому API будет осуществляться через клиент JavaScript.

С этой последней частью серверная часть готова, осталось только настроить приложение Angular.

Угловой клиент

Попробуем представить, что нужно

Пришло время создать внешнее клиентское приложение, однако перед этим, вероятно, будет разумнее перечислить все требования к этому приложению и возможные варианты использования.

Итак, нам нужно приложение, в котором пользователь будет переходить по определенному маршруту и ​​просматривать список имен домашних животных. Итак, это компонент, я назову его компонентом домашних животных. Однако эта страница должна быть защищена, поэтому мне нужно будет создать защиту маршрута, в обязанности которой входит проверка подлинности пользователя или нет. Если пользователь не аутентифицирован, он переходит на страницу входа. Для страницы входа нам понадобится новый компонент, который будет отображать реактивную форму в шаблоне и пытаться выполнить аутентификацию после отправки. Нам понадобится служба Angular для получения информации о статусе аутентификации пользователя и для выполнения танца аутентификации с сервером авторизации. Наконец, нам понадобится HTTP-перехватчик Angular, который добавит HTTP-заголовок Authorization с токеном-носителем в каждый запрос.

Чтобы пост был коротким, я покажу только самые интересные части приложения, полный код смотрите в репозитории GitHub.

Для создания каркаса и сборки этого приложения я использую Angular CLI, интерфейс командной строки для Angular.

Защищенная страница

Я начну с защищенной страницы и создам компонент с именем pets. Команда Angular CLI для этого

Код внутри компонента очень прост, я просто хочу сделать запрос GET к RESTful API и получить коллекцию имен домашних животных. Шаблон еще проще, поэтому я не буду показывать его детали.

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

Служба аутентификации

Я создал новую службу с помощью следующей команды Angular CLI.

Далее я добавлю необходимый код для выполнения описанных ранее действий.

Я пропустил большую часть кода внутри, я хотел бы пройтись по нему по частям. В приведенном выше коде я внедрил HttpClient Angular и JwtHelperService, сервис из пакета @auth0/angular-jwt. Он предоставляет хороших помощников, и этот сервис может помочь мне определить, истек ли срок действия токена. Я начну с метода аутентификации, который отвечает за аутентификацию владельца ресурса с помощью потока ROPC, а затем перейду к остальным методам.

Этот метод получает имя пользователя и пароль пользователя в своей подписи. Сначала я создаю коллекцию заголовков HTTP, включая только заголовок Content-Type для этого запроса. Затем я создаю тело, которое соответствует стандартам OAuth 2 для предоставления пароля. Я использую класс URLSearchParams для создания тела точно так же, как строка запроса. Обратите внимание, при вызове метода post я преобразовываю его в строку. Также обратите внимание, что я использую метод rxjs pipe и только один оператор конвейера, который является map, чтобы сохранить токен JWT в localStorage, как только я его получу. Хранить токен в локальном хранилище может быть не лучшей практикой, потому что приложение может быть подвержено XSS-атакам, но сейчас я воспользуюсь этим методом.

Затем мне нужен метод, который может проверить, аутентифицирован ли пользователь или нет. Это просто, нам нужно только получить токен из localStorage и убедиться, что срок его действия не истек.

Код в методе isAuthenticated — это прямой перевод требований в код. Здесь пригодится JwtHelperService, так как он может проверить, истек ли срок действия токена или нет. Методы отдыха, getToken и logout, говорят сами за себя.

Небольшое примечание: не забудьте зарегистрировать JwtModule в своем модуле приложения и настроить его. Требуется настроить свойство tokenGetter, значением которого является функция, возвращающая сохраненный токен.

Угловые охранники маршрута

Двигаясь вперед, теперь я могу реализовать защиту маршрута, снова используя команду Angular CLI.

Защита маршрута может использоваться для проверки некоторых URL-адресов маршрутизатора и может быть либо службой, либо функцией. Я буду использовать тип службы, который должен реализовать интерфейс CanActivate. Этот интерфейс имеет метод canActivate, который разрешает переданные текущие параметры маршрутизатора и возвращает boolean. Возвращаемое значение false означает, что маршрут не может быть активирован.

Теперь мне нужно поработать над методом canActivate сторожа, перенаправляющим пользователя на страницу входа, когда он не аутентифицирован. Чтобы получить эту часть информации, я буду использовать AuthService, который я создал ранее.

Обратите внимание, что в параметрах запроса в методе navigate я добавляю текущий URL-адрес состояния маршрутизатора, чтобы перенаправить пользователя обратно со страницы, с которой был отправлен запрос.

Охранник маршрута может быть зарегистрирован как провайдер в модуле приложения, и чтобы использовать его для определенного маршрута, используйте свойство canActivate маршрута, как в следующем примере в app-routing.module.ts.

Страница авторизации

Следующая необходимая часть функциональности — это страница входа в систему, защита маршрута перенаправляется на маршрут /login, который обрабатывается компонентом с именем AuthComponent, который можно создать с помощью следующей команды.

Реализация должна быть простой. Мне нужно будет создать форму, чтобы пользователь мог ввести имя пользователя и пароль, и после отправки я аутентифицирую пользователя, используя метод authenticate из AuthService.

Обратите внимание, что в ngOnInit происходят две вещи. Во-первых, мне нужно получить строку запроса returnUrl из URL-адреса, чтобы использовать ее позже, когда пользователь будет успешно аутентифицирован, чтобы перенаправить обратно на нее. Наконец, я создаю новую реактивную форму с двумя элементами управления формы, именем пользователя и паролем. Код в шаблоне тривиален, и я не буду вдаваться в подробности, вероятно, оставлю особенности реактивных форм для другого поста. Однако я перейду к методу ngSubmit, который вызывается при отправке формы.

Сначала я получаю значения в полях имени пользователя и пароля, которые затем передаю методу authenticate внедренного AuthService. Это возвращает Observable, поэтому мне нужно подписаться на него. В случае успешного разрешения пользователь перенаправляется обратно на returnUrl или домашнюю страницу. В противном случае пользователю будет показано сообщение об ошибке.

Угловые HTTP-перехватчики

Наконец, необходим перехватчик HTTP, чтобы добавить токен доступа в каждый заголовок запроса. До этого момента приложение может получать и хранить токен в браузере, но ничего интересного не может произойти, пока оно не отправит этот токен на сервер ресурсов, иначе защищенный ресурс не может быть получен.

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

Перехватчик HTTP может быть реализован как служба и должен реализовать интерфейс HttpInterceptor. Метод intercept принимает HttpRequest и HttpHandler, которые могут быть следующим обработчиком в цепочке для обработки HttpRequest. Создайте перехватчик как класс и вручную добавьте интерфейс и декоратор Injectable. Команда CLI будет

Пока реализация следующая

Если пользователь аутентифицирован, я получаю токен доступа, затем клонирую HttpRequest, обновляя его с помощью объекта заголовков HTTP, переданного в методе clone, и, наконец, передаю его методу handle следующего обработчика в цепочке, если таковой имеется.

Осталось зарегистрировать перехватчик в Angular DI как перехватчик HTTP, используя токен поставщика HTTP_INTERCEPTORS.

Все, все что нужно реализовано и приложение наконец-то готово.

Конечный результат

Это токен JWT, который приложение Angular получает от сервера авторизации. Обратите внимание на полезную нагрузку, она включает в себя все необходимые заявки, а также заявки на имя и адрес электронной почты, которые я настроил для TestUser ранее.

Вот так выглядит приложение

Резюме

В этом посте мы подробно рассмотрели поток учетных данных владельца ресурса и реализовали клиент OAuth 2, который его использует. Как и в предыдущем посте, настроить сервер авторизации не составило труда, и все благодаря фреймворку безопасности IdentityServer4 для ASP.NET. Затем, следуя аналогичным шагам, мы настроили сервер ресурсов в качестве веб-API ASP.NET Core, защитив определенные конечные точки с помощью токенов-носителей JWT. Последняя часть с клиентом Angular 6 потребовала довольно много работы, представив такие функции, как защита маршрутизации, перехватчики HTTP, реактивные формы, службы и HTTP-клиент Angular. Все это вместе создало секретный соус для окончательной защиты компонента pets, позволяя нам делать удаленные вызовы базового API для получения необходимых данных.

Как упоминалось ранее, поток ROPC может быть не идеальным в сценариях, где ваш API является общедоступным, без шифрования HTTPS, поскольку такой сценарий создает большие риски для безопасности. Кроме того, такой поток может быть не очень удобен для ваших пользователей, так как не существует концепции токена обновления. Когда срок действия токена истекает, пользователь автоматически выходит из системы и вынужден снова войти в систему, чтобы получить доступ, что в некоторых случаях может быть очень раздражающим. Представьте, что вы заполняете поля длинной формы, и как только вы нажимаете «Отправить», приложение выводит вас из системы, теряя все до этого момента. Не круто.

Ключом к разработке вашего приложения является тщательное изучение потребностей приложения и оценка стратегий безопасности, предоставляемых протоколами, разработанными для этой цели, такими как OAuth. Когда у вас есть все, реализация будет просто прямым переводом требований в код. Трудная и важная часть состоит в том, чтобы принять правильное решение, когда речь идет о безопасности, поскольку эта тема очень чувствительна, особенно для общедоступных API, где риски выше.

Если вам понравился этот блог, ставьте лайк и делитесь! Чтобы узнать больше, следите за мной в Twitter.

Этот пост является частью серии ASP.NET Core Authentication.

Первоначально опубликовано на codereform.com 19 августа 2018 г.