Аутентификация на основе токенов REST API


122

Я разрабатываю REST API, требующий аутентификации. Поскольку сама аутентификация происходит через внешний веб-сервис по HTTP, я решил, что мы будем выдавать токены, чтобы избежать повторного вызова службы аутентификации. Это подводит меня к моему первому вопросу:

Действительно ли это лучше, чем просто требовать от клиентов использовать HTTP Basic Auth для каждого запроса и кешировать вызовы на стороне сервера службы аутентификации?

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

В настоящее время токены приобретаются так:

curl -X POST localhost/token --data "api_key=81169d80...
                                     &verifier=2f5ae51a...
                                     &timestamp=1234567
                                     &user=foo
                                     &pass=bar"

api_key, timestampИ verifierтребуется все запросы. "Подтверждающий" возвращается:

sha1(timestamp + api_key + shared_secret)

Я намерен разрешить вызовы только от известных абонентов и предотвратить дословное повторное использование вызовов.

Это достаточно хорошо? Underkill? Overkill?

С токеном на руках клиенты могут приобретать ресурсы:

curl localhost/posts?api_key=81169d80...
                    &verifier=81169d80...
                    &token=9fUyas64...
                    &timestamp=1234567

Для самого простого возможного звонка это кажется ужасно многословным. Учитывая, что в shared_secretконечном итоге это будет встроено (как минимум) в приложение iOS, из которого, я полагаю, оно может быть извлечено, предлагает ли это вообще что-либо, кроме ложного чувства безопасности?


2
Вместо использования sha1 (timestamp + api_key + shard_secret) вы должны использовать hmac (shared_secret, timpestamp + api_key) для лучшего хеширования безопасности en.wikipedia.org/wiki/Hash-based_message_authentication_code
Мигель А. Карраско

@ MiguelA.Carrasco И, похоже, в 2017 году все пришли к единому мнению, что bCrypt - это новый инструмент хеширования.
Шон

Ответы:


94

Позвольте мне все разделить и решить каждую проблему изолированно:

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

Для аутентификации baseauth имеет то преимущество, что это зрелое решение на уровне протокола. Это означает, что многие проблемы, которые «могут возникнуть позже» , уже решены для вас. Например, с помощью BaseAuth пользовательские агенты знают, что пароль является паролем, поэтому они не кэшируют его.

Загрузка сервера аутентификации

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

Безопасность передачи

Если вы можете использовать SSL-соединение, это все, что вам нужно, это безопасное соединение *. Чтобы предотвратить случайное многократное выполнение, вы можете отфильтровать несколько URL-адресов или попросить пользователей включить в URL-адрес случайный компонент ("nonce").

url = username:key@myhost.com/api/call/nonce

Если это невозможно и передаваемая информация не является секретной, я рекомендую защитить запрос хешем, как вы предложили в подходе с использованием токена. Поскольку хэш обеспечивает безопасность, вы можете указать своим пользователям предоставить хеш в качестве пароля baseauth. Для повышения надежности я рекомендую использовать случайную строку вместо временной метки в качестве «одноразового номера», чтобы предотвратить атаки повторного воспроизведения (два законных запроса могут быть сделаны в течение одной секунды). Вместо того, чтобы предоставлять отдельные поля «общий секрет» и «ключ api», вы можете просто использовать ключ api в качестве общего секрета, а затем использовать соль, которая не изменяется, чтобы предотвратить атаки радужной таблицы. Поле имени пользователя кажется хорошим местом для размещения одноразового номера, так как это часть auth. Итак, теперь у вас есть такой чистый вызов:

nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:one_time_key@myhost.com/api/call

Правда, это немного утомительно. Это потому, что вы не используете решение на уровне протокола (например, SSL). Так что было бы неплохо предоставить пользователям какой-то SDK, чтобы, по крайней мере, им не приходилось проходить его самостоятельно. Если вам нужно сделать это таким образом, я считаю соответствующий уровень безопасности (just-right-kill).

Безопасное секретное хранилище

Это зависит от того, кому вы пытаетесь помешать. Если вы запрещаете людям, имеющим доступ к телефону пользователя, использовать вашу службу REST от имени пользователя, то было бы неплохо найти какой-то API-интерфейс связки ключей в целевой ОС и сохранить в SDK (или разработчике) ключ там. Если это невозможно, вы можете хотя бы немного усложнить получение секрета, зашифровав его и сохранив зашифрованные данные и ключ шифрования в отдельных местах.

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

(*) РЕДАКТИРОВАТЬ: SSL-соединения больше не должны считаться безопасными без дополнительных шагов для их проверки .


Спасибо, cmc, все хорошие моменты и отличная пища для размышлений. В итоге я выбрал подход с использованием токенов / HMAC, аналогичный тому, который вы обсуждали выше, скорее, как механизм аутентификации S3 REST API .
cantlin

Если вы кэшируете токен на сервере, разве он не такой же, как старый добрый идентификатор сеанса? Идентификатор сеанса недолговечен, и он также привязан к быстрому хранилищу кеша (если вы его реализуете), чтобы избежать попадания в вашу БД при каждом запросе. В истинном дизайне RESTful и без сохранения состояния не должно быть сеансов, но если вы используете токен в качестве идентификатора, а затем все еще обращаетесь к БД, не лучше ли вместо этого просто использовать идентификатор сеанса? В качестве альтернативы вы можете использовать веб-токены JSON, которые содержат зашифрованную или подписанную информацию для всех данных сеанса для истинного дизайна без сохранения состояния.
JustAMartin

16

Чистый RESTful API должен использовать стандартные функции базового протокола:

  1. Для HTTP RESTful API должен соответствовать существующим стандартным заголовкам HTTP. Добавление нового заголовка HTTP нарушает принципы REST. Не изобретайте колесо заново, используйте все стандартные функции в стандартах HTTP / 1.1, включая коды ответов о состоянии, заголовки и так далее. Веб-службы RESTFul должны использовать стандарты HTTP и полагаться на них.

  2. Сервисы RESTful ДОЛЖНЫ БЫТЬ БЕЗГРАЖДАННЫМИ Любые уловки, такие как аутентификация на основе токенов, которая пытается запомнить состояние предыдущих запросов REST на сервере, нарушают принципы REST. Опять же, это ОБЯЗАТЕЛЬНО; то есть, если ваш веб-сервер сохраняет любую информацию, относящуюся к контексту запроса / ответа, на сервере, пытаясь установить какой-либо сеанс на сервере, тогда ваш веб-сервис НЕ является Stateless. И если это НЕ без гражданства, это НЕ RESTFul.

Итог: для целей аутентификации / авторизации следует использовать стандартный заголовок авторизации HTTP. То есть вы должны добавлять заголовок HTTP-авторизации / аутентификации в каждый последующий запрос, который необходимо аутентифицировать. REST API должен соответствовать стандартам схемы HTTP-аутентификации. Особенности форматирования этого заголовка определены в стандартах RFC 2616 HTTP 1.1 - раздел 14.8 Авторизация RFC 2616 и в RFC 2617 HTTP-аутентификация: базовая и дайджест-аутентификация доступа. ,

Я разработал службу RESTful для приложения Cisco Prime Performance Manager. Найдите в Google документ REST API, который я написал для этого приложения, чтобы получить более подробную информацию о соответствии RESTFul API здесь . В этой реализации я решил использовать схему авторизации HTTP «Basic». - проверьте версию 1.5 или выше этого документа REST API и выполните поиск авторизации в документе.


8
«Добавление нового HTTP-заголовка нарушает принципы REST» Как так? И если вы это делаете, вы можете быть так любезны, чтобы объяснить, в чем именно разница (в отношении принципов) между паролем, срок действия которого истекает через определенный период, и токеном, срок действия которого истекает через определенный период.
Лучше Оливер

6
Имя пользователя + пароль - это токен (!), Которым клиент и сервер обмениваются при каждом запросе. Этот токен хранится на сервере и имеет время жизни. Если срок действия пароля истечет, мне придется приобрести новый. Кажется, вы связали «токен» с «сеансом сервера», но это неверный вывод. Это даже не имеет значения, потому что это будет деталь реализации. Ваша классификация токенов, отличных от имени пользователя / пароля, как имеющих состояние, является чисто искусственной, imho.
Лучше Оливер

1
Я думаю, вы должны мотивировать, почему нужно выполнять реализацию с RESTful вместо базовой аутентификации, которая является частью исходного вопроса. Возможно, вы могли бы также сослаться на несколько хороших примеров с включенным кодом. Новичку в этом предмете теория кажется достаточно ясной при наличии большого количества хороших ресурсов, но метод реализации - нет, а примеры запутаны. Меня расстраивает то, что для своевременной реализации того, что делалось тысячи раз,, кажется, требуется специальное кодирование.
JPK

13
-1 «Любые уловки, такие как аутентификация на основе токенов, которые пытаются запомнить состояние предыдущих запросов REST на сервере, нарушают принципы REST». Аутентификация на основе токенов не имеет ничего общего с состоянием предыдущих запросов REST и не нарушает безгражданство REST .
Керем Байдоган

1
Итак, согласно этому, веб-токены JSON являются нарушением REST, потому что они могут хранить состояние пользователя (утверждения)? В любом случае, я предпочитаю нарушать REST и использовать старый добрый идентификатор сеанса в качестве «токена», но первоначальная аутентификация выполняется с именем пользователя + проход, подписывается или зашифровывается с использованием общего секрета и очень короткой временной метки (поэтому он терпит неудачу, если кто-то пытается воспроизвести который). В «корпоративном» приложении сложно отказаться от преимуществ сеанса (избежать обращения к базе данных для некоторых данных, необходимых почти в каждом запросе), поэтому иногда нам приходится жертвовать истинным безгражданством.
JustAMartin

2

В Интернете протокол с отслеживанием состояния основан на наличии временного токена, которым обмениваются браузер и сервер (через заголовок cookie или перезапись URI) при каждом запросе. Этот токен обычно создается на стороне сервера и представляет собой кусок непрозрачного данных, у которых есть определенное время жизни, и его единственная цель - идентифицировать конкретного веб-агента пользователя. То есть токен является временным и становится СОСТОЯНИЕМ, которое веб-сервер должен поддерживать от имени клиентского пользовательского агента в течение этого разговора. Следовательно, такая связь с использованием токена НЕОБХОДИМА. И если диалог между клиентом и сервером СТАТИЧНЫЙ, это не RESTful.

Имя пользователя / пароль (отправленный в заголовке авторизации) обычно сохраняется в базе данных с целью идентификации пользователя. Иногда пользователь мог иметь в виду другое приложение; однако имя пользователя / пароль НИКОГДА предназначены для идентификации конкретного пользовательского агента веб-клиента. Разговор между веб-агентом и сервером, основанный на использовании имени пользователя и пароля в заголовке авторизации (после базовой авторизации HTTP), является БЕСПРОВОДНЫМ, потому что интерфейс веб-сервера не создает и не поддерживает какую-либо информацию о СОСТОЯНИИ.что бы то ни было от имени конкретного пользовательского агента веб-клиента. И, исходя из моего понимания REST, в протоколе четко указано, что диалог между клиентами и сервером должен быть БЕСПЛАТНЫМ. Следовательно, если мы хотим иметь настоящую службу RESTful, мы должны использовать имя пользователя / пароль (см. RFC, упомянутый в моем предыдущем сообщении) в заголовке авторизации для каждого отдельного вызова, а НЕ токен типа отправления (например, токены сеанса, созданные на веб-серверах. , Токены OAuth, созданные на серверах авторизации, и т. Д.).

Я понимаю, что несколько вызываемых REST-провайдеров используют токены, такие как OAuth1 или OAuth2 accept-tokens, которые передаются как «Авторизация: носитель» в заголовках HTTP. Однако мне кажется, что использование этих токенов для служб RESTful нарушит истинное БЕЗГРАНИЧНОЕ значение, которое охватывает REST; потому что эти токены представляют собой временный фрагмент данных, созданный / поддерживаемый на стороне сервера для идентификации конкретного пользовательского агента веб-клиента в течение допустимого периода разговора между веб-клиентом и сервером. Следовательно, любая служба, использующая эти токены OAuth1 / 2, не должна называться REST, если мы хотим придерживаться ИСТИННОГО значения протокола STATELESS.

Рубенс

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.