Реализация системы проверки подлинности без сохранения состояния в ASP.NET Core

При декомпозиции монолитного приложения на микросервисы проверка подлинности всегда представляет собой проблему. Упаковка всей логики управления безопасным доступом в общую базовую библиотеку вызовет сильную связь между микросервисами и максимизирует уязвимость вашей базы данных безопасности. Вместо этого мы можем реализовать службу токенов для обработки аутентификации с отслеживанием состояния в нашей службе.

Что такое токен-сервис?

Служба токенов - это веб-служба, единственная ответственность которой заключается в создании токенов-носителей (JWT) и предоставлении средств (JWKS) для проверки токенов. Служба токенов - единственная служба с разрешениями на чтение и запись базы данных авторизации. Информация авторизации надежно упакована в веб-токены JSON (JWT), так что любой сервер может определить, есть ли у клиента доступ, исключительно путем проверки токена и без каких-либо обращений к базе данных.

Зачем внедрять токен-сервис?

Хороший вопрос. Практически нет веских причин брать на себя ответственность за «прокатку собственного» токен-сервиса. Вместо этого используйте существующего поставщика удостоверений как услуги, например Okta, Auth0 или Azure Active Directory.

Однако если в вашей организации

  • устаревшие компоненты в вашей системе, которые не могут быть интегрированы с этими поставщиками удостоверений.
  • доступны специалисты по безопасности, которые могут провести тестирование на проникновение и контролировать безопасность вашего токен-сервиса

или вы просто хотите саботировать свою текущую команду с помощью бомбы замедленного действия из уязвимости системы безопасности, вы можете довольно легко создать и интегрировать службу токенов в ASP.NET Core.

Концепции

Мы используем несколько основных протоколов / стандартов, чтобы максимизировать безопасность при минимальном объеме кода и знаниях племен.

Безопасность и идентификация ASP.NET Core

Мы можем реализовать и интегрировать службу токенов, потому что ASP.NET Core и библиотека Microsoft.IdentityModel уже предоставляют все типы и возможности расширения. Вам нужно будет понять, как приложения ASP.NET Core реализуют аутентификацию и авторизацию, прежде чем мы сможем добавить нашу собственную аутентификацию и авторизацию службы токенов. Такие понятия, как ClaimsPrincipals и AuthenticationService, уникальны для этого стека.

Асимметричные JWT

Асимметрично подписанные JWT - это JWT, которые подписаны секретным закрытым ключом (в рамках службы токенов) и позже проверены открытым ключом (опубликованы службой токенов). Полезная нагрузка JWT - это типы утверждений сопоставления объектов JSON со значениями утверждений. Утверждения - это пары тип-значение, которые описывают предмет токена (сущность, запросившую токен). Поскольку JWT подписаны, их можно передавать в качестве токенов-носителей для использования, не требуя, чтобы получатель доверял отправителю - получателю нужно только проверить подпись токена по общедоступному набору веб-ключей JSON (JWKS), чтобы доверять его содержимому.

OpenID Connect Discovery

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

OAuth 2.0

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

Области действия

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

Сериализация области зависит от контекста. Обычно свойства scope содержат строку областей, разделенных пробелами, тогда как свойства scopes содержат массив строк области.

Стандартного формата для прицелов не существует. Я рекомендую ставить перед каждым идентификатором разрешения префикс имени службы. Например, область для обновления item ресурсов в службе myservice может выглядеть как myservice/item.update.

Последовательность запросов

Поток запросов разбивается на три меньших потока:

  • Клиент лениво извлекает и кэширует информацию о конечной точке токена из конечной точки обнаружения, используя ConfigurationManager<OpenIdConnectConfiguration>.
  • Сервер лениво извлекает и кэширует набор ключей из конечной точки обнаружения в AuthenticationService.
  • Клиент с нетерпением извлекает токен из конечной точки токена и прикрепляет его к запросам сервера в заголовке Authorization.

Хронология

  1. При запуске приложения клиент создает и кэширует ConfigurationManager<OpenIdConnectConfiguration>.
  2. Клиент запрашивает OpenIdConnectConfiguration у ConfigurationManager<OpenIdConnectConfiguration>, который, в свою очередь, лениво извлекает и кэширует OpenIdConnectConfiguration из конечной точки обнаружения.
  3. Клиент считывает конечную точку токена из OpenIdConnectConfiguration.
  4. Клиент отправляет запрос в конечную точку токена с некоторыми данными запроса, зависящими от приложения.
  5. Клиент запрашивает защищенную конечную точку, используя полученный токен носителя.
  6. Сервер AuthenticationService лениво извлекает и кэширует OpenIdConnectConfiguration из конечной точки обнаружения.
  7. Сервер AuthenticationService лениво извлекает и кэширует набор ключей службы токенов из конечной точки набора ключей, обнаруженной из OpenIdConnectConfiguration.
  8. Сервер AuthenticationService проверяет подпись токена-носителя по набору ключей. Если подпись недействительна, сервер возвращает 401.
  9. Сервер AuthenticationService анализирует утверждения JWT в ClaimsPrincipal HttpContext.
  10. Защищенная конечная точка получает запрос с предварительно заполненным ClaimsPrincipal. Отсюда он может предположить, что запрос безопасен, или провести дополнительную проверку ClaimsPrincipal.

Подготовка кластера для работы с токенами

Подготовьте пустой микросервис ASP.NET Core

Пользовательские службы токенов имеют смысл только в том случае, если у вас есть микросервисы. Вам понадобится новый проект ASP.NET Core, содержащий ваш новый микросервис токена. Микросервису потребуется собственный уникальный базовый URL. Мы будем называть этот уникальный идентифицирующий URL «Орган» или «Эмитент».

Интегрируйте поставщика удостоверений

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

Ваша реализация снова будет полностью зависеть от вашей текущей системы. Общие решения используют клиентские сертификаты или JWT.

Внедрение службы токенов

Добавление ключей подписи к внедрению зависимостей

Вашему сервису токенов необходим эксклюзивный доступ к набору сертификатов закрытых ключей. Как вы интегрируете эти сертификаты, полностью зависит от структуры инфраструктуры вашей команды. В этом руководстве предполагается, что вы добавляете свои закрытые ключи к внедрению зависимостей как IEnumerable<X509Certificate2>. Мы будем ссылаться на IEnumerable<X509Certificate2> в конструкторах наших внедренных зависимостей.

Определите некоторые константы

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

Определите свои контракты API: класс TokenRequest

Этот объект запроса должен включать (как минимум),

  • Идентификатор клиента, если вы используете идентификатор уровня приложения. Если вы используете клиентские сертификаты, сертификат вместо этого находится в HttpContext.Connection.ClientCertificate, а идентификатор клиента string будет HttpContext.Connection.ClientCertificate.Subject.Substring(3) (удаление префикса «CN =» из темы сертификата).
  • Объемы, запрошенные клиентом.

Определите свои контракты API: TokenResponse class

Для простоты в этом руководстве мы возвращаем токен string напрямую. Службы OAuth вернут объект Ответ токена доступа OAuth, содержащий метаданные о токене в дополнение к токену.

Определите свои контракты API: TokenError класс

Для простоты в этом руководстве мы возвращаем сообщение об ошибке string непосредственно вместе с 400 кодом состояния, если мы не можем выполнить аутентификацию и создать токен для клиента. Сервисы OAuth вернут объект Ответ об ошибке OAuth с кодом ошибки и сообщением об ошибке.

Добавьте контроллер конечной точки токена

Мы разместим токен-маршрут по адресу /token. Когда клиент отправляет POST на /token, служба токенов будет

  1. Подтвердите личность клиента. Если клиент отправил сертификат клиента, необходимо проверить цепочку сертификата и срок его действия; если клиент отправил JWT, необходимо проверить подпись и срок действия JWT.
  2. Вызовите метод авторизации, чтобы определить, есть ли у клиента доступ к запрошенным областям. Вероятно, это связано с вызовом базы данных для определения доступного для клиента набора областей.
  3. Получите претензии от клиента. Одним из требований будет объем, запрошенный клиентом. Это может включать вызов базы данных для поиска дополнительной информации о клиенте.
  4. Упакуйте утверждения в подписанный JWT.
  5. Отправьте JWT обратно клиенту.

Добавьте контроллер конечных точек набора ключей

Конечная точка набора ключей - это просто общедоступный объект JsonWebKeySet (JWKS), который мы получаем из наших подписывающих сертификатов. Схема JWKS определена в спецификации Веб-ключ JSON (JWK). ASP.NET Core AuthenticationService поддерживает кэшированный JWKS, который он использует для проверки подписей JWT, которые он получает.

Добавьте контроллер конечной точки обнаружения

ASP.NET Core AuthenticationService автоматически строит маршрут конечной точки обнаружения, добавляя «.well-known / openid-configuration» к Authority. AuthenticationService делает неаутентифицированный запрос к конечной точке обнаружения для определения URL-адреса JWKS.

Использование службы токенов

Клиент: получение токена

Чтобы получить токен, мы сначала обнаружим конечную точку токена из конфигурации обнаружения.

Затем клиент может отправить TokenRequest JSON в конечную точку токена и получить токен.

Сервер: аутентификация с помощью токена

Обновите свой ConfigureServices метод, чтобы определить и добавить схему аутентификации.

Дальнейшие действия

Авторизация клиентов в зависимости от области действия

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

Ротация сертификатов без перезапуска

В этом руководстве мы интегрировали наши сертификаты закрытых ключей в нашу службу, добавив IEnumerable<X509Certificate2> в Dependency Injection. Хотя это подходит для начальной итерации, вы должны заменить IEnumerable<X509Certificate2> своим собственным одноэлементным классом, который может лениво извлекать и кэшировать сертификаты закрытых ключей и периодически очищать кеш для реализации ротации сертификатов.

Токены кеша

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