Современные методы аутентификации: токен и это всё о нем!

Что такое оauth2.0?

Разработку нового Auth мы решили начать с изучения доступных протоколов и технологий. Самый распространённый стандарт авторизации — фреймворк авторизации OAuth2.0. 

Стандарт был принят в 2022 году, и за 8 лет протокол меняли и дополняли. RFC стало настолько много, что авторы оригинального протокола решили написать OAuth 2.1, который объединит все текущие изменения по OAuth 2.0 в одном документе. Пока он на стадии черновика.

Актуальная версия OAuth описанна в RFC 6749. Именно его мы и разберем. 

OAuth 2.0 — это фреймворк авторизации.

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

Особенности:

Разберёмся подробнее в особенностях.

. Менеджер аутентификации.


Менеджер аутентификации — это объект класса, реализующего интерфейс

org.springframework.security.authentication.AuthenticationManager

с единственным методом

authenticate()

. Данному методу нужно передать частично заполненный объект, реализующий интерфейс

org.springframework.security.core.Authentication

(контекстом безопасности приложения).


Задача менеджера аутентификации — в случае успешной аутентификации заполнить полностью объект

Authentication

и вернуть его. При заполнении нужно установить пользователя (

principal

), его права (

authorities

), выполнить

setAuthenticated(true)

. В случае неудачи менеджер аутентификации должен выбросить исключение

AuthenticationException

Приведём пример реализации интерфейса org.springframework.security.core.Authentication:

Java-токены

Смарт-карта, а точнее её чип, имплантированный в пластик или встроенный в корпус USB-ключа является полноценным компьютером в миниатюре: с жёстким диском (EEPROM), оперативной памятью (ROM), процессором и, конечно, операционной системой. Функционалом, операционной системой и «установленными» на неё приложениями и определяются возможности токена.

Предыдущие поколения токенов, как правило, использовали проприетарную лицензируемую операционную систему (один из монополистов этого рынка – компания Siemens с её CardOS). Закрытая архитектура делала крайне сложной разработку дополнительных приложений и компонентов самой операционной системы, например, реализацию поддержки национальных криптографических алгоритмов.

Современные токены строятся на базе Java-карты, являющейся стандартом на рынке (более 10 крупных производителей). Функциональность конкретного токена определятся набором загруженных апплетов, выполняющихся на виртуальной Java-машине. Открытая платформа и широкая популярность языка программирования Java позволяет разрабатывать и в короткие сроки внедрять новые возможности.

Среди перспективных разработок – реализация мобильного электронного кошелька пользователя, контроль целостности критических данных средствами апплета, выполняющего в заведомо доверенной среде смарт-карты и т.п.
Важной особенностью современных Java-токенов является поддержка USB CCID Class Driver.

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

USB CCID Class Driver встроен в операционную систему Windows Vista и автоматически скачивается с сайта Windows Update при обнаружении нового подключенного устройства в Windows XP, 2003.
Описанные технологии позволяют использовать современные токены, в отличие от устройств предыдущего поколения на более широком парке компьютерной техники и для решения более широкого спектра задач.

Централизованное управление средствами аутентификации

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

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

Типовыми сценариями, отнимающими дорогостоящее время системных администраторов, становятся сброс забытого PIN-кода токена, замена или выдача временного взамен повреждённого/утраченного токена.
При значительном количестве пользователей (от 100 и выше) затраты на сопровождение средств аутентификации в масштабах компании становятся сравнимыми со стоимостью владения централизованной системой управления жизненным циклом токенов.

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

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

Ручной учёт токенов, персонализация и правильное назначение их пользователям в зависимости от должностных обязанностей, а так же аудит использования и контроль правильности применения политик в масштабах крупной компании просто немыслим.
Описанные выше и многие другие задачи из соображений безопасности и в том числе  для уменьшения влияния человеческого фактора как правило автоматизируют с использованием специализированных систем класса  Token Management System (TMS).

Собственно, TMS – это система, предназначенная для внедрения, управления, использования и учета аппаратных средств аутентификации пользователей (USB-ключей и смарт-карт) в масштабах предприятия. С момента инициализации токена и до его отзыва, то есть на протяжении всего времени его функционирования в инфраструктуре компании, основным инструментом для управления им является TMS.

К базовым функциям TMS относятся: ввод в эксплуатацию токена (смарт-карты, USB-ключа, комбинированного USB-ключа или генератора одноразовых паролей), персонализация токена сотрудником, добавление возможности доступа к новым приложениям, а так же его отзыв, замена или временная выдача новой карты, разблокирование PIN-кода, обслуживание вышедшей из строя смарт-карты и отзыв её.

Итоги

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

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

Наличие у ведущих производителей токенов в России соответствующих лицензий и сертификатов на сами токены сделало возможным использование этой технологии в том числе и во многих государственных министерствах и ведомствах.
Средний и малый бизнес вслед за крупными компаниями и государством проявляют всё больший интерес к токенам как средствам сохранения конфиденциальности коммерческой информации.

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

Врезка – Пример внедрения

В апреле этого года Компания BCC, бизнес-партнер Aladdin Software Security R.D., завершила проект по модернизации информационной инфраструктуры Юридического факультета Санкт-Петербургского государственного университета. В рамках проекта, выполненного с применением продуктов и технологий корпорации Microsoft и средств аутентификации от Aladdin, факультет получил одну из наиболее совершенных информационных систем, функционирующих в российских государственных высших учебных заведениях.

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

Json web tokens

Учитывая указанные проблемы, в сообществе разработчиков много лет назад появилась идея использовать некую строку, которую с одной стороны невозможно подделать, с другой — которую каждый ресурсный сервер мог бы сам проверить на валидность. В качестве такой строки оказалось удобно использовать JWT — JSON Web Token.

JWT представляет собой строку из трех частей, разделенных точками. На иллюстрации ниже приведены значения каждой части:

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

Но никто не может прочитать записанное в третьей части — и соответственно никто не может подделать токен.

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

Перейдем к следующей схеме на иллюстрации ниже. Схема похожа на аутентификацию с помощью сессий, но есть одно отличие. После введения кредов сервер дает юзеру не cookies с Session ID, а токен. Затем клиентское приложение добавляет токен к каждому запросу в виде специального заголовка Authorization.

При этом оно вписывает в него слово «Bearer» (предъявитель), а после пробела — сам токен. Также на схеме представлен Auth middleware. Это достаточно стандартная часть любого фреймворка, которая умеет проверять, в частности, валидность токена. То есть обработка на сервере проходит этап проверки.

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

Logout и only one active device

Давайте подробнее рассмотрим задачи авторизации Logout и Only one active device. Сами по себе токены задуманы как stateless, то есть они не предназначены для размещения на сервере. А идея заключается в том, чтобы предусмотреть возможность хранения какой-то информации о них на сервере.

Такие токены будут храниться в «черном списке», пока не истечет срок их использования. С другой стороны, можно создать на сервере whitelist, что может оказаться даже несколько проще. Этот список станет реестром выданных токенов, с которым может сверяться система. Если предъявленный юзером токен не просрочен и указан в «белом списке», то он валиден.

Хранение самих токенов в базе данных сложно назвать безопасным. Можно ли как-то выйти из этой  ситуации? Да. Обратите внимание на иллюстрацию ниже: в разделе payload добавился пункт hash. Это некая рандомно сформированная строка. Она записывается в payload токена и в хранилище на сервере с привязкой к идентификатору пользователя.

Когда юзер предъявляет токен, система парсит его, проверяет срок действия токена и его подпись секретной строкой. Затем вытаскивает hash и сверяет его с пользовательским ID в хранилище. Это может быть key-value хранилище, основная база данных или другое хранилище, которое предоставит современный фреймворк.

Token authentication

Современные методы аутентификации: токен и это всё о нем!

Следующее поколение способов аутентификации представляет Token Based Authentication, который обычно применяется при построении систем Single sign-on (SSO). При его использовании запрашиваемый сервис делегирует функцию проверки достоверности сведений о пользователе другому сервису. Т. е. провайдер услуг доверяет выдачу необходимых для доступа токенов собственно токен-провайдеру (Identity provider).

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

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

На следующей схеме дополнительно отражены те этапы взаимодействия, в которых пользователь принимает непосредственное участие. Этот момент и является недостатком подобной схемы — нам всегда нужен пользователь, чтобы получить доступ к ресурсу.
Современные методы аутентификации: токен и это всё о нем!

Биометрия

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

Шаблоны представляют собой достаточно большие числовые последовательности; сам образец невозможно восстановить из шаблона. Контрольный шаблон и есть “пароль” пользователя.
Контрольный шаблон сравнивается с эталонным или зарегистрированным шаблоном, созданным на основе нескольких образцов определенной физиологической или поведенческой характеристики пользователя, взятых при его регистрации в биометрической системе.

Поскольку эти два параметра (контрольный и эталонный шаблон) полностью никогда не совпадают, то биометрической системе приходится принимать решение о том, “достаточно” ли они совпадают. Степень совпадения должна превышать определенную настраиваемую пороговую величину.

  1. подделка отличительной черты;
  2. воспроизведение поведения пользователя;
  3. перехват биометрических показателей;
  4. воспроизведение биометрической подписи.

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

Встроенная флэш-память

Несмотря на возможность с помощью печати на специальных принтерах превратить токен в форм-факторе смарт-карты в универсальное устройство, объединяющее бэйдж (с фотографией и ФИО владельца), средство аутентификации и карточку для прохода в помещения, в России наиболее распространённый форм-фактор подключаемых к компьютерам токенов – это USB-ключи.

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

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

Аппаратная реализация криптографических алгоритмов позволяет в одном корпусе объединить сами защищаемые данные и ключи шифрования, необходимые для доступа к ним.
Большой объём памяти (до 4 ГБ) позволяет размещать и автоматически запускать при подключении:

  1. Драйверы самого токена;
  2. Приложения безопасности (например, приложения для шифрования данных);
  3. Приложения, предназначенные специально для отдельных пользователей или групп пользователей;
  4. Операционные системы;
  5. Файлы установки.

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

Генераторы одноразовых паролей

Для работы вне стен офиса с необорудованных и ненастроенных рабочих мест, например, в интернет-кафе  использовать USB-ключи и смарт-карты фактически невозможно, особенно с учётом того, что последние помимо драйверов требуют наличия карт-ридера.
Использование классических «многоразовых» паролей является серьёзной уязвимостью при работе в таких недоверенных средах.

Это подтолкнуло ведущих вендоров рынка аутентификации к созданию аппаратных генераторов одноразовых паролей (ОТР-устройств, от англ. One Time Password). Такие устройства генерируют очередной пароль, который сотрудник вводит в окно запроса либо по расписанию (например, каждые 30 секунд)  либо по запросу (при нажатии на кнопку).

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

Генераторы одноразовых паролей появились до широкого распространения смарт-карт в ответ на растущее число инцидентов с кражей конфиденциальной информации при помощи удалённого доступа. Такой метод аутентификации не является строгим и носит название – усиленный.

Основная уязвимость одноразовых паролей – атака типа «человек посередине». При такой атаке злоумышленник вклинивается в коммуникацию между пользователем и сервером, полностью контролируя весь информационный обмен между ними. Отсутствие криптографических преобразований как в случае с использованием смарт-карт и цифровых сертификатов снижает уровень обеспечиваемой безопасности, позволяя использовать данный способ только в определённых случаях.

Так, например, банки в зависимости от метода аутентификации (по одноразовому паролю или цифровому сертификату) устанавливают различные лимиты на проведение операций.
Стоит отметить, что в России в связи с более поздним становлением рынка аппаратных токенов и из-за наличия к тому времени более совершенных смарт-карт, генераторы одноразовых паролей не так широко распространены, как, например, на западе.

Делегирование с областью видимости

Как API узнает, какой уровень доступа нужно дать приложению? Это определяется путем установления области видимости (scopes).

Область видимости «ограничивает, что именно приложение может делать в интересах пользователя». Она не позволяет выдать права, которых у пользователя уже нет. Например, если пользователь MyCalApp не имеет права создавать новые корпоративные аккаунты, область видимости гарантирует, что HireMe123 тоже не позволит пользователю создавать новые корпоративные аккаунты.

Области видимости делегируют контроль доступа самому API или ресурсу. За соответствие областей видимости правам пользователя отвечает API.

Давайте рассмотрим это на примере.

Я использую приложение HireMe123. HireMe123 хочет получить доступ к стороннему API MyCalApp для создания события в календаре от моего имени. Приложение HireMe123 уже запросило токен доступа к MyCalApp на сервере авторизации MyCalApp. В этом токене содержится важная информация:

  • sub: (мой пользовательский ID в MyCalApp);
  • aud: MyCalAppAPI (этот токен создан для доступа к API MyCalApp);
  • scope: write:events (область видимости предполагает, что HireMe123 может использовать API для записи событий в моем календаре).

HireMe123 посылает запрос к API MyCalApp с токеном доступа в заголовке авторизации. Когда API MyCalApp получает этот запрос, он видит, что в нем установлена область видимости write:events.

Но на MyCalApp содержатся аккаунты с календарями сотен тысяч пользователей. Поэтому, чтобы убедиться, что этот запрос от HireMe123 будет касаться только моих прав создавать события в моем аккаунте, промежуточное ПО API MyCalApp должно проверить не только область видимости, но и sub — идентификатор субъекта.

В контексте делегированной авторизации области видимости обозначают, что именно приложение сможет делать от имени пользователя. Это подмножество прав пользователя в целом.

Интеграция с системами управления доступом

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

Оставленный без присмотра компьютер до включения программы-заставки является серьёзной брешью в системе безопасности.
Для решения этой задачи в современные токены производители предлагают встраивать RFID-метки, полностью аналогичные тем, что встроены в бесконтактных проксимити-картах, так широко используемых в современных офисах.

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

Парольная аутентификация

Самый распространённый способ аутентификации – парольный. Однако слабая парольная защита не удовлетворяет современному уровню требований информационной безопасности. Надежность этого способа аутентификации в значительной степени зависит от человеческого фактора, то есть от того, насколько качественные ключевые слова будут выбирать пользователи и насколько серьезно они будут относиться к их хранению.

Часто сотрудники стараются упростить свою жизнь, нарушая при этом правила безопасности, и фактически, подчас сами того не сознавая, открывают злоумышленникам дорогу к «святая святых» – коммерческой информации компании.
Обратимся к аналитике:

Лишь 23% ИТ-сотрудников, опрошенных Ponemon Institute, высказали уверенность в том, что неструктурированные данные (например, текстовые документы), которыми обладает компания, надежно защищены. Наоборот, 84% заявило, что к конфиденциальной информации имеют доступ слишком многие, а 76% признало, что не располагают средствами контроля доступа сотрудников к данным.

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

Казалось бы, наоборот, система защиты встроена в ОС, никаких дополнительных затрат не нужно… Но обслуживание и сопровождение парольной защиты отнимает много времени у сотрудников компании, ответственных за работоспособность информационной системы. Им необходимо регулярно проводить аудит паролей пользователей, консультировать по правилам выбора и хранения паролей, производить замену паролей для профилактики, а также в случае их утери или забывчивости пользователей.

Подбор ключа симметричного алгоритма подписи

При использовании симметричных алгоритмов для подписи JWT (HS256, HS512 и др.) злоумышленник может попытаться подобрать ключевую фразу.

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

В нашем примере из первой части статьи для подписи JWT в качестве ключевой фразы была использована строка password. Она простая, короткая и содержится во всех основных словарях для перебора паролей. Злоумышленнику не составит труда подобрать эту ключевую фразу с использованием программ John the Ripper или hashcat.

Рекомендации для защиты от атаки в этом случае такие:

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

Постскриптум

В своей реализации Refresh токена использовал общую длину 24 знака. Первые 6 знаков – это дата его “протухания”, следующие 12 знаков – случайно сгенерированные данные. И в конце 6 знаков – это часть Access токена последней части сигнатуры.

Дату протухания внедрил прям в токен с той целью, чтобы не хранить эту информацию где-то в другом месте, например, в базе данных.

Дата содержит год, месяц, день, час и минуты. Хранится в ASCII

Кодирование даты на Golang:

// приводим к целочисленному числу uint32. Итого 32 бита.
// расчет простой: год 12 бит, месяц 4 бита, день 5 бит и т.д. Таким образом в аккурат умещаемся в 32 бита или 4 байта.
date := uint32(year<<20) | uint32(month<<16) | uint32(day<<11) | uint32(hour<<6) | uint32(minute)

// в цикле кодируем байты в ASCII. 1 знак это шесть бит. Итого и получаем шесть знаков даты по таблице ASCII – печатные знаки.
for n := 0; n < 6; n {
b6Bit = byte(date>>i) & 0x3F
sBuilder.WriteByte(byte8bitToASCII(b6Bit))

}

Всю реализацию на Go можно изучить на Github-е

Права доступа

Права доступа выдаются клиенту в виде scope. Scope – это параметр, который состоит из разделённых пробелами строк — scope-token.

Каждый из scope-token представляет определённые права, выдающиеся клиенту. Например, scope-token doc_read может предоставлять доступ на чтение к какому-то документу на resource server, а employee — доступ к функционалу приложения только для работников фирмы. Итоговый scope может выглядеть так: email doc_read employee.

В OAuth 2.0 мы сами создаём scope-token, настраивая их под свои нужды. Имена scope-token ограничиваются только фантазией и двумя символами таблицы ASCII — ” и .

На этапе регистрации клиента, в настройках сервиса авторизации клиенту выдаётся стандартный scope по умолчанию. Но клиент может запросить у сервера авторизации scope, отличный от стандартного. В зависимости от политик на сервере авторизации и выбора владельца ресурса, итоговый набор scope может выглядеть совсем иначе.

Проблемы

Первая версия Auth — часть монолита. Он использует свой собственный протокол общения с сервисами. Такая «схема» была необходима в тот момент, но за несколько лет работы проявились проблемы.

Auth — часть монолита. Следовательно, сервис привязан к релизному циклу, что лишает возможности независимой разработки и деплоя. Кроме того, придется разворачивать весь монолит, если захотелось развернуть Auth, например, при масштабировании сервиса.

Dodo IS зависит от Auth. В старой реализации внешние сервисы обращаются к Auth при каждом действии пользователя, чтобы валидировать данные о нём. Настолько сильная привязка может привести к остановке работы всей Dodo IS, если Auth «приляжет» по какой-то причине.

Auth зависит от Redis. Притом достаточно сильно — неисправность работы Redis’а приведёт к падению Auth’а. Мы используем Azure Redis, для которого заявленный SLA 99,9%. Это значит, что сервис может быть недоступен до 44 минут в месяц. Такие простои не позволительны.

Текущая реализация Auth использует свой протокол аутентификации, не опираясь на стандарты. В большинстве своих сервисов мы используем C# (если говорим о backend) и у нас нет проблем с поддержкой библиотеки для нашего протокола. Но если вдруг появятся сервисы на Python, Go или Rust, разработка и поддержка библиотек под эти языки потребует дополнительных затрат времени и принесет дополнительные сложности.

Текущий Auth использует схему Roles Based Access Control, которая базируется на ролях. Обычно с ролью выдаётся полный доступ к определённому сервису, вместо привязки к конкретному функционалу. Например, в пиццериях есть заместители управляющего, которые могут вести определенные проекты: составлять графики или учитывать сырьё.

Проблемы подтолкнули к тому, чтобы спроектировать и написать новую версию Auth. На старте проекта мы потратили 3 недели только на изучение стандартов авторизации и аутентификации OAuth 2.0 и OpenID Connect 1.0. 

Примечание. Утрированно, статья — это пересказ RFC, который приходилось перечитывать несколько раз, чтобы понять, что происходит вокруг. Здесь я постарался уйти от этой сложности и рассказать всё максимально просто, структурировано, кратко и без описания сложных вещей, например, какие символы может содержать в себе ответ сервиса.

Токены

Токен в OAuth 2.0 — это строка, непрозрачная для клиента. Обычно строка выглядит как случайно сгенерированная — её формат не имеет значения для клиента. Токен — это ключ доступа к чему-либо, например, к защищённому ресурсу (access token) или к новому токену (refresh Token).

У каждого токена своё время жизни. Но у refresh token оно должно быть больше, т.к. он используется для получения access token. Например, если срок жизни access token около часа, то refresh token можно оставить жить на целую неделю. 

Refresh token опционален и доступен только для confedential клиентов. Пользуясь опциональностью токена, в некоторых реализациях время жизни access token сделано очень большим, а refresh token вообще не используется, чтобы не заморачиваться с обновлением.

За access token закреплён определённый набор прав доступа, который выдаётся клиенту во время авторизации. Давайте разберёмся, как выглядят права доступа в OAuth 2.0.

Формат jwt

JWT состоит из трех основных частей: заголовка (header), нагрузки (payload) и подписи (signature). Заголовок и нагрузка формируются отдельно в формате JSON, кодируются в base64, а затем на их основе вычисляется подпись. Закодированные части соединяются друг с другом, и на их основе вычисляется подпись, которая также становится частью токена.

Бывают и исключения, когда в JWT отсутствует подпись. Подобный случай будет рассмотрен далее.

Заголовок является служебной частью и состоит из двух полей: типа токена, в данном случае JWT, и алгоритма хэширования подписи:

{
  "typ": "JWT",
  "alg": "HS256"
}

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

Поле alg обязательно для заполнения. В приведенном случае был применен алгоритм HS256 (HMAC-SHA256), в котором для генерации и проверки подписи используется единый секретный ключ.

Для подписи JWT могут применяться и алгоритмы асимметричного шифрования, например RS256 (RSA-SHA256). Стандарт допускает использование и других алгоритмов, включая HS512, RS512, ES256, ES512, none и др.

Использование алгоритма none указывает на то, что токен не был подписан. В подобном токене отсутствует часть с подписью, и установить его подлинность невозможно.

Закодируем этот JSON в base64 и получим: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Функция для получения токена:


function getTokenData(login, password) {
    return fetch('api/auth', {
        method: 'POST',
        credentials: 'include',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            login,
            password,
        }),
    })
        .then((res) => {
            if (res.status === 200) {
                const tokenData = res.json();
                saveToken(JSON.stringify(tokenData)); // сохраняем полученный токен в sessionStorage, с помощью функции, заданной ранее
                return Promise.resolve()
            }
            return Promise.reject();
        });
}

Таким образом мы получили токен с полями

«access_token»«refresh_token»«expires_in»

и сохранили его в

sessionStorage

для дальнейшего использования.

Функция-обертка


export async function fetchWithAuth(url, options) {
    
    const loginUrl = '/login'; // url страницы для авторизации
    let tokenData = null; // объявляем локальную переменную tokenData

    if (sessionStorage.authToken) { // если в sessionStorage присутствует tokenData, то берем её
        tokenData = JSON.parse(localStorage.tokenData);
    } else {
       return window.location.replace(loginUrl); // если токен отсутствует, то перенаправляем пользователя на страницу авторизации
    }

    if (!options.headers) { // если в запросе отсутствует headers, то задаем их
        options.headers = {};
    }
    
    if (tokenData) {
        if (Date.now() >= tokenData.expires_on * 1000) { // проверяем не истек ли срок жизни токена
            try {
                const newToken = await refreshToken(tokenData.refresh_token); // если истек, то обновляем токен с помощью refresh_token
                saveToken(newToken);
            } catch () { // если тут что-то пошло не так, то перенаправляем пользователя на страницу авторизации
               return  window.location.replace(loginUrl);
            }
        }

        options.headers.Authorization = `Bearer ${tokenData.token}`; // добавляем токен в headers запроса
    }

    return fetch(url, options); // возвращаем изначальную функцию, но уже с валидным токеном в headers
}

С помощью кода выше мы создали функцию, которая будет добавлять токен к запросам в api. На эту функцию мы можем заменить fetch в нужных нам запросах, где требуется авторизация и для этого нам не потребуется менять синтаксис или добавлять в аргументы еще какие-либо данные.

Просто достаточно будет «импортнуть» ее в файл и заменить на нее стандартный fetch.

import fetchWithAuth from './api';

function getData() {
    return fetchWithAuth('api/data', options)
}

Хранение токенов на стороне клиента

Если приложение хранит токен так, что возникает одна или несколько из следующих ситуаций:

Для предотвращения атаки:

  1. Хранить токен в браузере, используя контейнер sessionStorage.
  2. Добавить его в заголовок Authorization, используя схему Bearer. Заголовок должен выглядеть следующим образом:
    Authorization: Bearer <token>
  3. Добавить fingerprint информацию к токену.

Сохраняя токен в контейнере sessionStorage, он предоставляет токен для кражи в случае XSS. Однако fingerprint, добавленный в токен, предотвращает повторное использование украденного токена злоумышленником на его компьютере. Чтобы закрыть максимум областей использования для злоумышленника, добавьте Политику безопасности содержимого браузера (Content Security Policy), чтобы ограничить контекст выполнения.

Остается случай, когда злоумышленник использует контекст просмотра пользователя в качестве прокси-сервера, чтобы использовать целевое приложение через легитимного пользователя, но Content Security Policy может предотвратить связь с непредвиденными доменами.

Также возможно реализовать службу аутентификации таким образом, чтобы токен выдавался внутри защищенного файла cookie, но в этом случае должна быть реализована защита от CSRF.

Явное аннулирование токена пользователем

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

Одним из способов защиты является внедрение черного списка токенов, который будет пригоден для имитации функции «выход из системы», существующей в традиционной системе сеансов.

В черном списке будет храниться сборник (в кодировке SHA-256 в HEX) токена с датой аннулирования, которая должна превышать срок действия выданного токена.

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

Пример реализации:

Хранилище черного списка:Для централизованного хранения черного списка будет использоваться база данных со следующей структурой:

create table if not exists revoked_token(jwt_token_digest varchar(255) primary key,
revokation_date timestamp default now());

Управление аннулированиями токенов:

// Контролирование отката токена (logout).
// Используйте БД, чтобы разрешить нескольким экземплярам проверять
// отозванный токен и разрешить очистку на уровне централизованной БД.
public class TokenRevoker {

 // Подключение к БД
 @Resource("jdbc/storeDS")
 private DataSource storeDS;

 // Проверка является ли токен отозванным
 public boolean isTokenRevoked(String jwtInHex) throws Exception {
     boolean tokenIsPresent = false;
     if (jwtInHex != null && !jwtInHex.trim().isEmpty()) {
         // Декодирование токена
         byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex);

         // Вычисление SHA256 от токена
         MessageDigest digest = MessageDigest.getInstance("SHA-256");
         byte[] cipheredTokenDigest = digest.digest(cipheredToken);
         String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest);

         // Поиск токена в БД
         try (Connection con = this.storeDS.getConnection()) {
             String query = "select jwt_token_digest from revoked_token where jwt_token_digest = ?";
             try (PreparedStatement pStatement = con.prepareStatement(query)) {
                 pStatement.setString(1, jwtTokenDigestInHex);
                 try (ResultSet rSet = pStatement.executeQuery()) {
                     tokenIsPresent = rSet.next();
                 }
             }
         }
     }

     return tokenIsPresent;
 }

// Добавление закодированного в HEX токена в таблица отозванных токенов
public void revokeToken(String jwtInHex) throws Exception {
     if (jwtInHex != null && !jwtInHex.trim().isEmpty()) {
         // Декодирование токена
         byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex);

         // Вычисление SHA256 от токена
         MessageDigest digest = MessageDigest.getInstance("SHA-256");
         byte[] cipheredTokenDigest = digest.digest(cipheredToken);
         String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest);

         // Проверка на наличие токена уже в БД и занесение в БД в
	   // обратном случае
         if (!this.isTokenRevoked(jwtInHex)) {
             try (Connection con = this.storeDS.getConnection()) {
                 String query = "insert into revoked_token(jwt_token_digest) values(?)";
                 int insertedRecordCount;
                 try (PreparedStatement pStatement = con.prepareStatement(query)) {
                     pStatement.setString(1, jwtTokenDigestInHex);
                     insertedRecordCount = pStatement.executeUpdate();
                 }
                 if (insertedRecordCount != 1) {
                     throw new IllegalStateException("Number of inserted record is invalid,"   " 1 expected but is "   insertedRecordCount);
                 }
             }
         }

     }
 }

Заключение первой части

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

Stay tuned.

Спойлер второй части

Минимальная реализация интеграция Identity Server в ваше приложение выглядит так:

public void Configuration(IAppBuilder app)
{
    var factory = new IdentityServerServiceFactory();
    factory.UseInMemoryClients(Clients.Get())
           .UseInMemoryScopes(Scopes.Get())
           .UseInMemoryUsers(Users.Get());

    var options = new IdentityServerOptions
    {
        SiteName = Constants.IdentityServerName,
        SigningCertificate = Certificate.Get(),
        Factory = factory,
    };

    app.UseIdentityServer(options);
}

Минимальная реализация интеграции веб-клиента с Identity Server:

public void Configuration(IAppBuilder app)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = "Cookies"
    });

    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    {
        ClientId = Constants.ClientName,
        Authority = Constants.IdentityServerAddress,
        RedirectUri = Constants.ClientReturnUrl,
        ResponseType = "id_token",
        Scope = "openid email",
        SignInAsAuthenticationType = "Cookies",
    });
}

Минимальная реализация интеграции веб-API с Identity Server:

public void Configuration(IAppBuilder app)
{
    app.UseIdentityServerBearerTokenAuthentication(
        new IdentityServerBearerTokenAuthenticationOptions
        {
            Authority = Constants.IdentityServerAddress,
            RequiredScopes = new[] { "write" },
            ValidationMode = ValidationMode.Local,

            // credentials for the introspection endpoint
            ClientId = "write",
            ClientSecret = "secret"
        });

    app.UseWebApi(WebApiConfig.Register());
}

Похожее:  Банк СГБ (СеверГазБанк): онлайн вход в личный кабинет

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *