Аутентификация REST и предоставление ключа API

94

Я читал о REST, и есть много вопросов об этом SO, а также о многих других сайтах и ​​блогах. Хотя я никогда не видел, чтобы этот конкретный вопрос задавался ... по какой-то причине я не могу осмыслить эту концепцию ...

Если я создаю RESTful API и хочу защитить его, один из методов, которые я видел, - использовать токен безопасности. Когда я использовал другие API, был токен и общий секрет ... имеет смысл. Чего я не понимаю, так это то, что запросы к сервису отдыха выполняются через javascript (XHR / Ajax), что должно помешать кому-то вынюхивать это с помощью чего-то простого, например FireBug (или "просмотра исходного кода" в браузере) и копировать ключ API, а затем выдавать себя за этого человека, используя ключ и секрет?

tjans
источник
один из методов, которые я видел, - это использование токена безопасности , существует действительно много методов. У тебя есть конкретный пример. Я могу подумать, что вы запутались с «REST» и «сделать доступным API javascript только для зарегистрированных пользователей» (например, карты Google).
PeterMmm
1
Поскольку вы спросили почти 2 года назад: что вы в конечном итоге использовали сами?
Arjan
На самом деле я ничего не использовал, я просто пытался осмыслить создание концепций. Комментарий PeterMmm, приведенный выше, вероятно, верен ... до сих пор не было необходимости реализовывать что-либо из этого, но я хотел стать лучше ... спасибо за продолжение.
tjans

Ответы:

22

api secret не передается явно, секрет используется для генерации знака текущего запроса, на стороне сервера сервер генерирует знак после того же процесса, если два знака совпадают, то запрос аутентифицируется успешно - так что только Знак передается по запросу, а не по секрету.

Джеймс Сюй
источник
9
Итак, если это просто переданный знак ... разве это не все еще отображается в javascript ... поэтому, если я помещаю мерцающую фотографию на свою веб-страницу через их API (вызываемый javascript), и вы посещаете мою страницу, не так ли? t Я раскрываю свой ключ API всем, кто посещает мою страницу?
tjans
6
Я не думаю, что задаю свой вопрос правильно ... возможно, это одна из причин, по которой я изначально не нашел то, что искал. когда я делаю свой вызов ajax, скажем, используя jquery, мне пришлось бы встроить ключ api в вызов ajax, чтобы он передавался на сервер ... в этот момент кто-то может увидеть ключ API. Если я неправильно понимаю, как ключ API отправляется с запросом, если он не встроен в клиентский скрипт?
tjans
4
В заключение: людям будет назначена пара apikey + apisecret перед использованием openapi / restapi, знак apikey + будет перенесен на сервер, чтобы убедиться, что сервер знает, кто делает запрос, apisecret никогда не будет передан на сервер в целях безопасности .
James.Xu
7
Итак, утверждение @ James.Xu о том, что «секрет используется для генерации знака текущего запроса», ЛОЖНО! Поскольку клиент не знает секрета, потому что было бы небезопасно отправлять его ему (а как еще он мог бы это узнать?) «Секрет», который технически является «закрытым ключом», используется ТОЛЬКО СЕРВЕРОМ (потому что никто другой этого не знает), чтобы создать знак, который будет сравниваться со знаком клиента. Итак, вопрос: какие данные объединяются с «ключом api», который никто не знает, кроме клиента и сервера? Знак = api_key + что?
ACs
1
Вы правы, @ACs. Даже если оба сервера (веб-сайт и сторонний API) знают один и тот же секрет, нельзя вычислить некоторую подпись на сервере веб-сайта, а затем поместить этот результат в HTML / JavaScript, а затем заставить браузер передать его API. При этом любой другой сервер может запросить этот HTML-код с первого веб-сервера, получить подпись из ответа и использовать ее в HTML на своем собственном веб-сайте. (Я действительно думаю, что вышеприведенный пост не отвечает на вопрос о том, как открытый ключ API в HTML может быть безопасным.)
Арджан
62

Мы представляем API, который партнеры могут использовать только в доменах, которые они у нас зарегистрировали. Его контент частично является общедоступным (но желательно только для показа в известных нам доменах), но в основном является частным для наших пользователей. Так:

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

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

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

  1. Когда нас get-csrf-token.js?apiKey=abc123просят:

    1. Найдите ключ abc123в базе данных и получите список допустимых доменов для этого ключа.

    2. Найдите файл cookie проверки CSRF. Если он не существует, сгенерируйте безопасное случайное значение и поместите его в файл cookie сеанса только для HTTP . Если файл cookie действительно существует, получите существующее случайное значение.

    3. Создайте токен CSRF из ключа API и случайного значения из файла cookie и подпишите его . (Вместо того, чтобы хранить список токенов на сервере, мы подписываем значения. Оба значения будут читаться в подписанном токене, это нормально.)

    4. Установите ответ так, чтобы он не кэшировался, добавьте файл cookie и верните сценарий, например:

      var apiConfig = apiConfig || {};
      if(document.domain === 'expected-domain.com' 
            || document.domain === 'www.expected-domain.com') {
      
          apiConfig.csrfToken = 'API key, random value, signature';
      
          // Invoke a callback if the partner wants us to
          if(typeof apiConfig.fnInit !== 'undefined') {
              apiConfig.fnInit();
          }
      } else {
          alert('This site is not authorised for this API key.');
      }
      

    Примечания:

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

    • Та же политика происхождения для JavaScript гарантирует, что браузер не может использовать XHR (Ajax) для загрузки, а затем проверки исходного кода JavaScript. Вместо этого обычный браузер может загружать его только с помощью <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(или динамического эквивалента), а затем запускает код. Конечно, ваш сервер не должен поддерживать совместное использование ресурсов Cross-Origin или JSONP для сгенерированного JavaScript.

    • Сценарий браузера может изменить значение document.domainперед загрузкой указанного выше сценария. Но та же политика происхождения только позволяет сократить домен путем удаления префиксов, как переписывание subdomain.example.comпросто example.com, или myblog.wordpress.comна wordpress.com, или в некоторых браузерах , даже bbc.co.ukв co.uk.

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

  2. Когда пользователь входит в систему, установите какой-нибудь пользовательский cookie-файл любым удобным для вас способом. (Пользователь мог уже войти в систему до того, как был запрошен JavaScript.)

  3. Все последующие запросы API к серверу (включая запросы GET и JSONP) должны включать токен CSRF, файл cookie проверки CSRF и (если выполнен вход) файл cookie пользователя. Теперь сервер может определить, следует ли доверять запросу:

    1. Наличие действительного токена CSRF гарантирует, что JavaScript был загружен из ожидаемого домена, если он загружен браузером.

    2. Наличие токена CSRF без файла cookie проверки указывает на подделку.

    3. Наличие как токена CSRF, так и файла cookie проверки CSRF ничего не гарантирует: это может быть либо поддельный запрос на стороне сервера, либо действительный запрос из браузера. (Это не может быть запрос от браузера, сделанный из неподдерживаемого домена.)

    4. Наличие файла cookie пользователя гарантирует, что пользователь вошел в систему, но не гарантирует, что пользователь является членом данного партнера или что пользователь просматривает правильный веб-сайт.

    5. Наличие файла cookie пользователя без файла cookie проверки CSRF указывает на подделку.

    6. Наличие файла cookie пользователя гарантирует, что текущий запрос будет выполнен через браузер. (Предполагая, что пользователь не будет вводить свои учетные данные на неизвестном веб-сайте, и предполагая, что мы не заботимся о том, чтобы пользователи использовали свои собственные учетные данные для выполнения какого-либо запроса на стороне сервера.) Если у нас также есть файл cookie проверки CSRF, то этот файл cookie проверки CSRF был также получил с помощью браузера. Затем, если у нас также есть токен CSRF с действующей подписью, ислучайное число в файле cookie проверки CSRF совпадает с числом в этом токене CSRF, тогда код JavaScript для этого токена также был получен во время того же самого раннего запроса, во время которого был установлен файл cookie CSRF, следовательно, также с использованием браузера. Это также означает, что приведенный выше код JavaScript был выполнен до того, как был установлен токен, и что в то время домен был действителен для данного ключа API.

      Итак: теперь сервер может безопасно использовать ключ API из подписанного токена.

    7. Если в какой-то момент сервер не доверяет запросу, возвращается 403 Forbidden. Виджет может отреагировать на это, показывая пользователю предупреждение.

Подписывать файл cookie проверки CSRF не требуется, поскольку мы сравниваем его с подписанным токеном CSRF. Отсутствие подписи файла cookie делает каждый HTTP-запрос короче, а проверка сервера - немного быстрее.

Сгенерированный токен CSRF действителен бесконечно, но только в сочетании с проверочным файлом cookie, так что эффективно, пока браузер не будет закрыт.

Мы можем ограничить время жизни подписи токена. Мы могли бы удалить файл cookie проверки CSRF, когда пользователь выходит из системы, чтобы выполнить рекомендацию OWASP . А чтобы не передавать случайное число для каждого пользователя нескольким партнерам, можно добавить ключ API к имени файла cookie. Но даже в этом случае невозможно легко обновить файл cookie проверки CSRF при запросе нового токена, поскольку пользователи могут просматривать один и тот же сайт в нескольких окнах, совместно используя один файл cookie (который при обновлении будет обновлен во всех окнах, после чего Токен JavaScript в других окнах больше не будет соответствовать этому единственному файлу cookie).

Для тех, кто использует OAuth, см. Также OAuth и клиентские виджеты , из которых я почерпнул идею JavaScript. Для использования API на стороне сервера , когда мы не можем полагаться на код JavaScript для ограничения домена, мы используем секретные ключи вместо открытых ключей API.

Арджан
источник
1
При использовании CORS, возможно, это можно безопасно расширить. Вместо этого при обработке предварительно запущенного OPTIONSзапроса с некоторым открытым ключом API в URL-адресе сервер может сообщить браузеру, какие домены разрешены (или отменить запрос). Однако помните, что для некоторых запросов не требуется предварительный запрос или вообще не используется CORS , а для CORS требуется IE8 +. Если для IE7 используется резервная Flash-версия, то, возможно, какая-то динамика crossdomain.xmlможет помочь добиться того же для этого. Мы еще не пробовали CORS / Flash.
Arjan
11

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

  1. У клиента есть открытый ключ, которым можно поделиться с кем угодно, не имеет значения, поэтому вы можете встроить его в javascript. Это используется для идентификации пользователя на сервере.
  2. Сервер имеет секретный ключ, и этот секрет ДОЛЖЕН быть защищен. Следовательно, аутентификация с общим ключом требует, чтобы вы могли защитить свой секретный ключ. Таким образом, общедоступный клиент javascript, который подключается напрямую к другой службе, невозможен, потому что вам нужен серверный посредник для защиты секрета.
  3. Сервер подписывает запрос с использованием некоторого алгоритма, который включает секретный ключ (секретный ключ похож на соль) и, предпочтительно, временную метку, затем отправляет запрос в службу. Отметка времени предназначена для предотвращения атак «повторного воспроизведения». Подпись запроса действительна только около n секунд. Вы можете проверить это на сервере, получив заголовок отметки времени, который должен содержать значение отметки времени, включенной в подпись. Если эта метка времени истекла, запрос не выполняется.
  4. Сервис получает запрос, содержащий не только подпись, но и все поля, подписанные в виде обычного текста.
  5. Затем служба подписывает запрос таким же образом, используя общий секретный ключ, и сравнивает подписи.
Крис
источник
Верно, но по умолчанию ваш ответ не раскрывает ключ API. Однако в некоторых API-интерфейсах ключ API является общедоступным, и вот в чем вопрос: «запросы к операции [...] службы отдыха, сделанные через javascript (XHR / Ajax)» . (Я чувствую, что принятый ответ неверен и в этом отношении; ваш пункт 2 об этом ясен, хорошо.)
Арьян
2

Я постараюсь ответить на вопрос в исходном контексте. Итак, вопрос: «Безопасно ли размещать секретный (API) ключ в JavaScript?

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

На мой взгляд, решение состоит в том, что вызов JavaScript по существу передает запрос внутреннему компоненту сервера, который отвечает за выполнение вызова отдыха. Компонент внутреннего сервера, допустим, сервлет будет считывать ключ API из защищенного источника, такого как файловая система на основе разрешений, вставлять в заголовок HTTP и выполнять внешний вызов rest.

Надеюсь, это поможет.

MG Разработчик
источник
1

Я полагаю, вы имеете в виду ключ сеанса, а не ключ API. Эта проблема унаследована от протокола http и известна как захват сеанса . Обычный обходной путь, как и на любом веб-сайте, - перейти на https.

Чтобы запустить службу REST в безопасности, вы должны включить https и, возможно, аутентификацию клиента. Но в конце концов, это выходит за рамки идеи REST. REST никогда не говорит о безопасности.

ПитерМмм
источник
8
Я действительно имел в виду ключ. Если я правильно помню, чтобы использовать API, вы передаете ключ API и секрет остальной службе для аутентификации, верно? Я знаю, что как только он будет передан по сети, он будет зашифрован с помощью SSL, но прежде, чем он будет отправлен, это будет прекрасно видно по клиентскому коду, который его использует ...
tjans
1

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

Идентификатор сеанса передается только один раз, и он ДОЛЖЕН быть через SSL.

См. Пример здесь

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

Иэн Портер
источник
1
Но как может быть какой-либо вход в систему, когда третье лицо использует ваш API? Если пользователь собирается войти в систему, все просто: просто использовать сеанс? Но когда другим веб-сайтам требуется пройти аутентификацию в вашем API, это не помогает. (Кроме того, это очень похоже на продвижение вашего блога.)
Арджан