Oauth в мобильных приложениях / хабр

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

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

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

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

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

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

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

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

Что такое сессии?

Попробую рассказать в паре абзацев.

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

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

Что такое хеширование

Отпечаток (хэш) — это результат работы функции хэширования, которая вернёт для любого значения строку фиксированной длины.Используя специальный математический алгоритм, такая функция умеет преобразовывать любую переданную информацию к строке фиксированной длины (например, 32 или 64 символа).

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

Возьмём простой пример. У нас есть информация, для которой мы хотим получить отпечаток. Пусть такой информацией будет следующая строка:

«Я знаю только то, что ничего не знаю, но другие не знают и этого»

Результат обработки этой строки хэширующей функцией SHA-1 будет таким:6b3cb0df50fe814dee886b4e1c747dda6ce88b37

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

Что же это такое?

Процитируем Википедию:

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

идентифицирован

 (мы должны знать, кто он) и

аутентифицирован

 (подтверждена его идентичность).

Реализация авторизации при разработке корпоративной информационной системы или продукта, ориентированного на корпоративный сектор — очень сложная и ответственная задача, которой часто уделяется недостаточное внимание на этапе проектирования и первичном этапе разработки, что в будущем ведет к «костыльной» реализации.

Что такое grant?

Grant — это данные, которые представляют из себя успешную авторизацию клиента владельцем ресурса, используемые клиентом для получения access token.

Например, когда мы где-либо аутентифицируемся с помощью Google, перед глазами всплывает уведомление. В нём говорится, что такой-то сервис хочет получить доступ к данным о вас или к вашим ресурсам (выводятся запрашиваемые scope-token). Это уведомление называется «Consent Screen».

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

Существует 4 1 способа получения grant — grant type:

Что делать дальше?

И вот мы подбираемся к самому интересному: а где хранить пароли и в каком виде? Давайте сначала подумаем, где их хранить.

Формируем request для обновления токена

val refreshRequest = TokenRequest.Builder(
   authServiceConfig,
   AuthConfig.CLIENT_ID
)
   .setGrantType(GrantTypeValues.REFRESH_TOKEN)
   .setScopes(AuthConfig.SCOPE)
   .setRefreshToken(TokenStorage.refreshToken)
   .build()

Тут нам важно учесть 2 строчки:

.setGrantType(GrantTypeValues.REFRESH_TOKEN)
.setRefreshToken(TokenStorage.refreshToken)

В качестве grantType передаем refreshToken, и передаем непосредственно сам refreshToken из вашего хранилища, который был получен при авторизации.

Формируем request:

val endSessionRequest = EndSessionRequest.Builder(authServiceConfig)
	//Требуется для некоторых сервисов, idToken получается при авторизации аналогично accessToken и refreshToken
  .setIdTokenHint(idToken) 
  // uri на который произойдет редирект после успешного логаута, не везде поддерживается
  .setPostLogoutRedirectUri(AuthConfig.LOGOUT_CALLBACK_URL.toUri()) 
  .build()

Выполняем сформированный request:

authorizationService.performTokenRequest(refreshRequest) { response, ex ->
   when {
       response != null -> emitter.onSuccess(response)
       ex != null -> emitter.tryOnError(ex)
       else -> emitter.tryOnError(IllegalStateException("response and exception is null"))
   }
}

Формируем custom tabs intent:

val customTabsIntent = CustomTabsIntent.Builder().build()

Открываем страницу логаута:

private val logoutResponse = registerForActivityResult(
	ActivityResultContracts.StartActivityForResult()
) {}

logoutResponse.launch(endSessionIntent)

Пользователь переходит на страницу логаута, где чистится его сессия в браузере.

После логаута нам нужно перехватить редирект, чтобы вернуться в приложение. Не все сервисы позволяют указывать URL редиректа после логаута (github не позволяет). Поэтому пользователю нужно будет нажать на крестик в CCT.

После ручного закрытия активити с CCT мы получим result=cancelled, потому что редиректа в приложение не было.

В нашем примере с Github мы будем в любом случае очищать сессию и переходить на страницу входа в приложение.

Authorization code

Самый распространённый flow на данный момент. В основном используется для confidential клиентов, но с появлением дополнительной проверки с помощью PKCE, может применяться и для public-клиентов. 

Browser

Второй вариант — открыть страницу во внешнем браузере, установленном на устройстве.

Преимущества:

Недостатки:

Chromecustomtabs, safarivc

ChromeCustomTabs(CCT) и SafariViewController(SafariVC) аналогично браузеру позволяют легко реализовать открытие веб-страниц в вашем приложении.

Они закрывают недостатки WebView:

Недостатки браузера тоже частично закрываются:

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

Этот подход является самым оптимальным. Он закрывает почти все недостатки предыдущих двух подходов.

Cookies

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

Mitm-атака для перехвата кода

Одной из реализаций OAuth является реализация с помощью внешнего браузера.

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

Именно в момент, когда система ищет приложение для обработки URL редиректа, возможен перехват редиректа зловредным приложением. Злоумышленник может создать приложение, которое перехватывает такие же редиректы, как у вас. Утекают все данные, которые находятся в строке редиректа.

Перехват редиректа в мобильном приложении
Перехват редиректа в мобильном приложении

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

При использовании обычного Authorization Code Flow чужое приложение (Malicious app) потенциально может получить код и обменять его на токен, аналогично тому, как это сделано в вашем приложении (Real app). Но с использованием code_verifier и code_challenge зловредный перехват становится бессмысленным.

Стоит отметить, что такая атака не сработает, если использовать universal links (ios) и applink (android). Чтобы открыть редирект-ссылку в приложении, необходимо положить на сервер json-файл с описанием подписи вашего приложения.

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

Oauth и flow

Когда речь идет про авторизацию и аутентификацию, используются такие понятия как OAuth2 и OpenID. В статье я не буду их раскрывать, на Хабре уже есть такой материал:

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

В OAuth существуют различные flow, но не все подходят для использования в приложении:

Resource owner password credentials flow


По текущим рекомендациям безопасности описанных в

, данный flow не рекомендуется использовать вовсе из-за явных проблем с безопасностью.

Webview

Преимущества:

Недостатки:

Абстрактный oauth 2.0. flow c применением access token

Мы рассмотрели роли, рассмотрели виды токенов, а также как выглядят scope. Посмотрим на flow предоставления доступа к сервису.

Ниже представлена абстрактная схема (или flow) взаимодействия между участниками. Все шаги на данной схеме выполняются строго сверху вниз. Разберём детальнее.

Клиент получает одобрение от resource owner, на основе которого ему выдаётся доступ к ресурсу. Всё просто. А будет ли так же просто, если мы добавим в эту схему работу с refresh token?

Абстрактный oauth 2.0. flow c применением refresh token


Первый и второй шаги опущены из данной схемы — они ничем не отличаются от схемы абстрактного flow выше.

Схема подробнее:

Авторизация

Теперь откроем страницу авторизации с использованием CCT.

Для работы с CCT и выполнения автоматических операций обмена кода на токен библиотека AppAuth предоставляет сущность AuthorizationService. Эта сущность создается при входе на экран. При выходе с экрана она должна очиститься. В примере это делается внутри ViewModel экрана авторизации.

Создаем в init:

private val authService: AuthorizationService = AuthorizationService(getApplication())

Очищаем в onCleared:

authService.dispose()

Для открытия страницы авторизации в CCT нужен интент. Для этого получаем AuthorizationRequest на основе заполненных раньше данных в AuthConfig:

private val serviceConfiguration = AuthorizationServiceConfiguration(
   Uri.parse(AuthConfig.AUTH_URI),
   Uri.parse(AuthConfig.TOKEN_URI),
   null, // registration endpoint
   Uri.parse(AuthConfig.END_SESSION_URI)
)

fun getAuthRequest(): AuthorizationRequest {
   val redirectUri = AuthConfig.CALLBACK_URL.toUri()
   return AuthorizationRequest.Builder(
       serviceConfiguration,
       AuthConfig.CLIENT_ID,
       AuthConfig.RESPONSE_TYPE,
       redirectUri
   )
       .setScope(AuthConfig.SCOPE)
       .build()
}

Создаем интент:

// тут можно настроить вид chromeCustomTabs
val customTabsIntent = CustomTabsIntent.Builder().build()

val openAuthPageIntent = authService.getAuthorizationRequestIntent(
   getAuthRequest(),
   customTabsIntent
)

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

Поэтому используем ActivityResultContracts. Также можно использовать startActivityForResult.

private val getAuthResponse = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
   val dataIntent = it.data ?: return
   handleAuthResponseIntent(dataIntent)
}

getAuthResponse.launch(openAuthPageIntent)

Под капотом будут открыты активити из библиотеки, которые возьмут на себя ответственность открытия CCT и обработку редиректа. А в активити вашего приложения уже прилетит результат операции.

Схема взаимодействия активити
Схема взаимодействия активити

Внутри openAuthPageIntent будет зашита вся информация, которую мы раньше указывали в AuthConfig, а также сгенерированный code_challenge.

Архив обновлен 24.02.2022г.

Внимание:
Если вы используете данный скрипт на локальном сервере типа
DENWER XAMPP
, то не
стоит ждать писем на свой почтовый ящик. Письма лежат в заглушке
sendmail
. В
Denwer
вы их можете
найти по пути
Z:tmp!sendmail
открыть данные файлы вы сможете в любом почтовом клиенте.

Аутентификация

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

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

Браузер отсутствует

В Android, в отличие от iOS, может не быть браузера. Но он нам понадобится для использования CCT, причем с поддержкой этого способа.

Кроме Chrome, этот функционал поддерживается в SBrowser, Firefox и всех остальных современных браузерах. Но даже если такового у пользователя нет, откроется обычный браузер. 

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

Варианты реализации oauth

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

Существует несколько вариантов реализации. 

Выбираем правильное хэширование

Идею хранения паролей нашли, то есть хранения не паролей, а их хэшей. А вот какой алгоритм хэширования выбрать?

Давайте посмотрим на то, что пробовали выше – простая функция md5. Алгоритма его расшифровки нет, но тем не менее md5 не рекомендуется для использования. Почему?

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

    select password from passwords where hash='e10adc3949ba59abbe56e057f20f883e';

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

Помимо md5 есть множество алгоритмов хэширования, sha256, sha512 и еще целая толпа. Их сложность выше, но это не отменяет опять-таки существования таблиц с готовыми паролями.
Нужно что-то хитрее.

Выход с сайта

Если на сайте есть вход, то должен быть и выход. Таким выходом будет специальный сценарий, который очистит сессию и переадресует на главную страницу.Чтобы очистить сессию, достаточно очистить массив $_SESSION:$_SESSION = []

Ещё немного терминологии

Следует различать два термина: аутентификация и авторизация.

Заглушка для авторизации

Функции, связанные с авторизацией, будут лежать в отдельном файле и своем пространстве имен. Создадим файл auth.php в api/v1/common – там, где уже лежит helpers.php.
Если вы разбирали уроки админки, особенно третий, про серверную часть, то эти пути вам будут знакомы. Если у вас свой проект, то кладите auth.php куда удобно.
Главное, потом правильно указать пути.


Содержимое auth.php

Задача auth

Проблема авторизации в десятках сервисов встречалась ещё несколько лет назад — в начале «

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

У сервиса Auth есть три основные задачи:

Использовать sdk сервиса, через который вы хотите авторизоваться

Плюсы:

Минусы:

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

Как прочитать куки

В PHP максимально упрощён процесс чтения информации из кукисов. Все переданные сервером куки становятся автоматически доступны в специальном глобальном массиве $_COOKIEТак, чтобы получить содержимое куки с именем «visit_count», достаточно обратиться к одноимённому элементу массива $_COOKIE, например вот так:

Обратите внимание: установив в сценарии куку через setcookie, прочитать её можно будет только при следующем посещении страницы.

Как установить куки: функция setcookie

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

  • Название этой куки (может состоять только из символов латинского алфавита и цифр);
  • Значение, которое предполагается хранить;
  • Срок жизни куки — это обязательное условие.

За установку куки в PHP отвечает функция setcookie, ей нужно передать как минимум три параметра, описанных выше. Пример:

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

Как устроены сессии

  1. PHP генерирует уникальный идентификатор браузера.
  2. Идентификатор сохраняется в специальную куку и передаётся с каждым запросом.
  3. Все данные, которые записываются в сессию, PHP автоматически сохраняет в специальном файле на сервере.

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

Перепишем сценарий для подсчета посещений, но теперь используем сессии:

Клиент

    Cookie:

  • уникальный идентификатор юзера
  • хэш

Логаут

В большинстве случаев при пользовательском логауте в приложении нужно почистить токены/файлы/БД/кеши.

Обновление токенов

С использованием OAuth вам не нужно забывать об обновлении токенов.

Общая настройка

Первым делом зарегистрируем приложение OAuth в Github.

При регистрации установите CALLBACK_URL для вашего приложения на сервисе. На этот URL будет происходить перенаправление после авторизации, и ваше приложение будет его перехватывать.

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


Права доступа выдаются клиенту в виде 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 может выглядеть совсем иначе.

Пример

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

Принцип работы authorization code flow with pkce

Для мобильных клиентов рекомендуется использовать Authorization Code Flow c дополнением: Authorization Code Flow with Proof Key for Code Exchange (PKCE). Использовать именно этот flow важно для безопасности пользовательского входа в приложение. Рассмотрим его особенности.

Этот flow основан на обычном Authorization Code Flow. Сначала вспомним его реализацию:

Проблематика

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

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

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

  1. Пользователь, не имеющий отношения к конкретному договору, не должен его видеть в системе
  2. Автор договора должен видеть его на всех этапах.
  3. Создавать договор имеет право пользователь с грейдом не ниже 10.
  4. Визирующий должен видеть договор, начиная с поступления к нему на этап и далее.
  5. Руководители подразделений должны видеть договоры своих подразделений вверх по иерархии.
  6. Автор договора и руководитель подразделения имеют право отозвать договор на любом этапе согласования.
  7. Руководство и секретариат головного офиса должны видеть документы всех филиалов.
  8. Пользователь, создавший договор, не должен иметь права его завизировать.

От безопасности мы могли бы получить следующие требования

  1. Знать, кто имеет доступ к конкретному договору.
  2. Знать, кто имел доступ к конкретному договору в заданный момент времени.
  3. Лишать пользователя доступа к ранее доступным документам при изменении его должностных обязанностей.

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

Итак, обозначим, почему эти требования проблемные:

Проблемы

Первая версия 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, который приходилось перечитывать несколько раз, чтобы понять, что происходит вокруг. Здесь я постарался уйти от этой сложности и рассказать всё максимально просто, структурировано, кратко и без описания сложных вещей, например, какие символы может содержать в себе ответ сервиса.

Проверяем работоспособность vue

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

Но попробуем пересобрать админку, чтобы убедиться, что все хорошо. Сначала в production режиме

    npm run build

Пути решения

Решить данную задачу нам помогают разработанные модели управления доступом:

MAC — мандатная модель управления доступом. Доступ субъекта к объекту определяется его уровнем доступа: уровень доступа субъекта должен быть не ниже уровня секретности объекта.

DAC — прямое управление доступом. Доступ субъекта к объекту определяется наличием субъекта в списке доступа (ACL) объекта.

RBAC — ролевая модель управления доступом. Доступ субъекта к объекту определяется наличием у субъекта роли, содержащей полномочия, соответствующие запрашиваемому доступу.

АВАС — атрибутивная модель управления доступом. Доступ субъекта к объекту определяется динамически на основании анализа политик учитывающих значения атрибутов субъекта, объекта и окружения. Сюда же относятся PBAC, RAdAC, CBAC, с некоторыми нюансами (шикарный обзор от CUSTIS).

Их крайне рекомендуется использовать в первозданном виде, не изобретая велосипед. Достаточно часто в сложных информационных системах используется комбинация моделей. Например, популярна комбинация ACL RBAC, когда роль включается в список доступа. Однако, правильное применение готовых моделей — тоже не простая задача.

Для реализации озвученных выше в разделе «Проблематика» требований, на первый взгляд, я бы выбрал комбинацию PBAC ACL. Требования могли бы быть реализованы следующим образом:

Перечисленные требования ИБ без всяких проблем реализуются с использованием ACL, однако бизнес-требования 5 и 7 мы реализуем с помощью PBAC. Так что для реализации требований ИБ 1 и 2 придется добавить к ним журнал или аналогичный механизм, поскольку при всей своей красоте динамические правила плохо аудируются:

Реализация android

Подключим библиотеку в проект:

implementation 'net.openid:appauth:0.9.1'

Запишем все настройки OAuth в один объект, чтобы было легко с ним работать:

Реализация в android-приложении

Давайте посмотрим, как можно реализовать OAuth в вашем Android-приложении с использованием AppAuth. Весь код доступен на Github.

Приложение простое: отображение информации о моем github-профиле.

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

При реализации нам необходимо разобраться с 3 ключевыми моментами:

Реализация регистрации пользователя

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

$passwordHash = password_hash('iloveponies', PASSWORD_DEFAULT);

Вызов этой функции вернёт следующую строку: $2y$10$a1pgDBerBsqD24D7qOAsl.QFTvwxQQGe4r1oWhD7f9yEnDvx4i7tWИменно это значение и следует хранить в БД, вместо пароля.

Реализовать вручную

Реализовать логику вручную внутри собственного приложения с использованием WebView или других реализаций (CCT/SafariVC).

Плюс:

Минус:

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

Регистрация клиента


Способ регистрации клиента, например, ручной или service discovery, вы выбираете сами, в зависимости от

фантазии

конкретной реализации. Но при любом способе при регистрации, кроме ID клиента, должны быть обязательно указаны 2 параметра: redirection URI и client type.

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

Client type — тип клиента, от которого зависит способ взаимодействия с ним. Тип клиента определяется его возможностью безопасно хранить свои учётные данные для авторизации — токен. Поэтому существует всего 2 типа клиентов:

Регистрация на сайте

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

Редирект в chrome не срабатывает

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

В процессе тестирования реализации OAuth в Android мы столкнулись с тем, что Chrome с использованием CCT после успешной авторизации не перебрасывал нас обратно в приложение на некоторых устройствах. На это заведен баг в трекере. 

В Chrome сделали обновление, которое запрещает без пользовательского намерения переходить по URL с кастомной схемой. Это блокирует попадание пользователя в зловредное приложение. 

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

Собираем всё вместе

Теперь, научившись устанавливать и читать куки, напишем полноценный сценарий, который будет считать и выводить количество посещений страницы пользователем:

Структура таблицы: bez_reg


--
-- Структура таблицы `bez_reg`
--

CREATE TABLE IF NOT EXISTS `bez_reg` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `login` varchar(200) NOT NULL,
  `pass` varchar(32) NOT NULL,
  `salt` varchar(32) NOT NULL,
  `active_hex` varchar(32) NOT NULL,
  `status` int(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Токены

Токен в 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.

Файл reg_form.html

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

Хранение паролей

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

Вместо вывода

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

Если хотите погрузиться в тематику детальнее, то рекомендую в RFC 6749 (для OAuth 2.0) и RFC 8628 (для Device Flow). Кроме того, следить за актуальными версиями RFC можно на ресурсе, посвящённому OAuth.

Если статья была полезна и захотите подробностей — пишите в комментариях, и в следующих статьях расскажу о PKCE, о протоколе аутентификации OpenID Connect 1.0, о нашей реализации сервера аутентификации и многом другом.

Полезные ссылки:

Заключение

Код проекта в статье находится в моем репозитории на GitHub.

Мы рассмотрели нюансы реализации OAuth в мобильных приложениях и пример реализации в Android-приложении с помощью библиотеки AppAuth. Эта реализация позволит вам быстро засетапить OAuth в вашем приложении.

По нашему опыту, AppAuth позволяет упростить работу с OAuth в приложении, избавляя вас от написания деталей имплементаций. Однако она предъявляет требования к серверу авторизации. Если сервис не соответствует RFC-8252 (OAuth 2.0 for Native Apps),  возможно, AppAuth покроет не весь требуемый функционал.

А как вы реализовывали OAuth в мобильных приложениях? были ли у вас проблемы? Использовали ли AppAuth?

Формируем итоговый интент:

val endSessionIntent = authService.getEndSessionRequestIntent(
    endSessionRequest,
    customTabsIntent
)

Вместо заключения

Проблемы с dev режимом во vue – это неприятный момент. Мы хотим и апишечку подергать, и все удобства vue-cli использовать. И на елку влезть, и ничего не ободрать.
Возможно, есть более изящный способ обойти эти проблемы в dev режиме, но я их пока не нашел. Поэтому приходится подпирать код лишними условиями.

Итого

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

Мы планируем серию статей, посвященных моделям авторизации и предмету в целом. Будем рады вопросам и предложениям по темам для рассмотрения.

Похожее:  Working with SOAP based Web Service using Python | by AYUSHI SHARMA | Medium

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

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