Веб-сервис RESTful - как аутентифицировать запросы от других сервисов?

117

Я разрабатываю веб-службу RESTful, к которой должны иметь доступ пользователи, а также другие веб-службы и приложения. Все входящие запросы должны быть аутентифицированы. Все коммуникации происходят по HTTPS. Аутентификация пользователя будет работать на основе токена аутентификации, полученного путем POST-отправки имени пользователя и пароля (через SSL-соединение) в ресурс / session, предоставленный службой.

В случае клиентов веб-службы за клиентской службой нет конечного пользователя . Запросы инициируются запланированными задачами, событиями или некоторыми другими компьютерными операциями. Список подключаемых услуг известен заранее (очевидно, догадываюсь). Как мне аутентифицировать эти запросы, поступающие от других (веб) сервисов? Я хочу, чтобы процесс аутентификации был максимально простым для реализации для этих служб, но не за счет безопасности. Каковы были бы стандартные и передовые методы для подобного сценария?

Варианты, которые я могу придумать (или которые были мне предложены):

  1. Сделайте так, чтобы клиентские службы использовали «поддельные» имя пользователя и пароль и аутентифицировали их так же, как и пользователей. Мне этот вариант не нравится - мне кажется, он не подходит.

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

  3. Я мог бы добавить к предыдущему варианту проверку IP-адреса. Это затруднит выполнение поддельных запросов.

  4. Сертификаты клиентов. Настроить собственный центр сертификации, создать корневой сертификат и создать клиентские сертификаты для клиентских служб. Однако на ум приходит пара вопросов: а) как мне разрешить пользователям аутентифицироваться без сертификатов и б) насколько сложен этот сценарий для реализации с точки зрения клиентского сервиса?

  5. Что-то еще - должны быть другие решения?

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

Tommi
источник
Если я могу предложить, ознакомьтесь с услугами большого веб-сайта и выберите то, что вам нравится. Ваши пользователи также найдут сходство с лучшими практиками других служб RESTful.
Измир Рамирес
Нашел еще один вопрос (почти двухлетний), касающийся аналогичной темы: stackoverflow.com/questions/1138831/…
Tommi
На какой ОС размещены сервисы (как Интернет, так и другие)? Работают ли они на серверах, входящих в одну инфраструктуру?
Андерс Абель
ОС может быть разной: Win, * nix и т. Д. И клиентские службы могут находиться или не находиться в той же инфраструктуре, что и моя служба.
Tommi

Ответы:

34

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

Я не думаю, что ваш пункт А относительно решения с сертификатом клиента трудно разрешить. Вы просто используете ветку. if (client side certificat) { check it } else { http basic auth }Я не эксперт по Java и никогда не работал с ним, чтобы делать сертификаты на стороне клиента. Однако быстрый поиск в Google приводит нас к этому руководству, которое смотрит прямо в ваш переулок.

Несмотря на все эти обсуждения «что лучше», позвольте мне просто указать, что есть еще одна философия, которая гласит: «Меньше кода, меньше ума - лучше». (Я лично придерживаюсь этой философии). Решение клиентского сертификата похоже на большой объем кода.

Я знаю, что вы задавали вопросы о OAuth, но предложение OAuth2 действительно включает решение вашей проблемы, называемое « токенами-носителями », которые должны использоваться вместе с SSL. Я думаю, что для простоты я бы выбрал либо жестко запрограммированного пользователя / прохода (по одному на приложение, чтобы их можно было отозвать индивидуально), либо очень похожие токены-носители.

newz2000
источник
27
Клиентские сертификаты НЕ являются общим секретом. Вот почему они существуют. У клиента есть закрытый ключ, а у сервера - открытый ключ. Клиент никогда не раскрывает свой секрет, и открытый ключ не является секретом.
Тим
5
Ссылка на учебное пособие ведет не к учебной статье, а к некоторой индексной странице Java на сайте Oracle ...
Марьян Венема,
2
@MarjanVenema Ну, это потому, что вы пытаетесь перейти по ссылке через 2 года после ответа newz2000, но вы всегда можете попробовать WayBack Machine: web.archive.org/web/20110826004236/http://java.sun.com/…
Fábio Duque Silva
1
@MarjanVenema: Мне очень жаль, но вы ожидаете, что newz2000 придет сюда и обновит ссылку после того, как она отключится? Как вы сказали, это гниение ссылок, так что рано или поздно это произойдет. Либо вы попытаетесь получить доступ к архиву, чтобы увидеть, что автор видел в то время, либо вы найдете новую ссылку и внесете положительный вклад. Не понимаю, как ваш комментарий кому-то помог. Но здесь перейдите по этой ссылке: oracle.com/technetwork/articles/javase/… (будьте осторожны, он тоже со временем сгниет)
Фабио Дуке Силва
2
@ FábioSilva: Нет. Я не ожидаю, что он так сделает. Я прочитал архив, но у меня не было времени искать новую ссылку, поэтому я сделал следующее лучшее: добавил комментарий, что она мертва, чтобы кто-нибудь из сообщества мог найти новое местоположение и обновить Почта. Поскольку у вас, очевидно, было время найти новую ссылку, почему вы не обновили ссылку в сообщении вместо того, чтобы поместить ее в призывавший меня комментарий?
Марьян Венема
36

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

Вот пример создания токена аутентификации:

(day * 10) + (month * 100) + (year (last 2 digits) * 1000)

например: 3 июня 2011 г.

(3 * 10) + (6 * 100) + (11 * 1000) = 
30 + 600 + 11000 = 11630

затем соедините с паролем пользователя, например "my4wesomeP4ssword!"

11630my4wesomeP4ssword!

Затем выполните MD5 этой строки:

05a9d022d621b64096160683f3afe804

Когда вы вызываете запрос, всегда используйте этот токен,

https://mywebservice.com/?token=05a9d022d621b64096160683f3afe804&op=getdata

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

Надежда помогает

:)

kororo
источник
1
Мне очень нравится, как вы добавляете токен безопасности в каждый запрос, но что с этим происходит, когда программист уже создал 100 страниц jsp и после этого t0 должен реализовать безопасность на ранее созданных 100 страницах, а также на страницах, которые будут созданы. В этом случае добавление токена в каждый запрос - неправильный выбор. В любом случае +1 для вашей техники. :)
Анкур Верма
4
Что произойдет, если часы не синхронизированы? Не будет ли клиент в этом случае сгенерировать неправильный токен? Даже если оба генерируют дату и время в формате UTC, их часы все равно могут отличаться, что приводит к появлению окна времени каждый день, когда токен не будет работать?
NickG
@NickG, у меня была эта проблема раньше, единственный способ защитить это, запросив время сервера. Это на 99% решает проблему UTC. Конечно, недостатком является дополнительный вызов сервера.
kororo
но я все еще могу использовать ваш веб-сервис с помощью этого токена в течение дня, верно? Я не понимаю, как это поможет
Мина Габриэль
@MinaGabriel, вы можете добавить больше таймфрейма в генерацию токена. (минута * 10) + (час * 100) + (день * 1000) + (месяц * 10000) + (год (последние 2 цифры) * 100000)
kororo
11

Есть несколько разных подходов.

  1. Пуристы RESTful захотят, чтобы вы использовали аутентификацию BASIC и отправляли учетные данные по каждому запросу. Их логика в том, что никто не хранит состояние.

  2. Клиентская служба может хранить файл cookie, который поддерживает идентификатор сеанса. Я лично не нахожу это таким оскорбительным, как некоторые пуристы, от которых я слышу - повторная аутентификация может быть дорогостоящей. Похоже, вам не очень нравится эта идея.

  3. Судя по вашему описанию, вы действительно можете быть заинтересованы в OAuth2. По моему опыту, из того, что я видел, это немного сбивает с толку и является своего рода передним краем. Существуют реализации, но их очень мало. Я понимаю, что в Java он интегрирован в модули безопасности Spring3 . (Их руководство красиво написано.) Я ждал, будет ли расширение в Restlet , но до сих пор, хотя оно было предложено и может быть в инкубаторе, оно все еще не было полностью включено.

jwismar
источник
Я ничего не имею против варианта 2 - я считаю, что это хорошее решение для приложения RESTful, - но откуда клиентская служба вообще берет токен? Как они проходят аутентификацию в первый раз? Может быть, я ошибаюсь, но кажется странным, что клиентской службе для этого нужно иметь собственное имя пользователя и пароль.
Tommi
Если конечный пользователь является вашим пользователем, то промежуточная служба может передать вам свои учетные данные по первому запросу, и вы можете вернуть файл cookie или другой токен.
jwismar
Точно так же в сценарии OAuth конечный пользователь делегирует промежуточной службе доступ к вашей веб-службе.
jwismar
Похоже, это недоразумение - за клиентской службой вообще нет конечного пользователя . Я обновил свой вопрос, чтобы лучше объяснить ситуацию.
Tommi
1
Я бы просто добавил, что вариант №1, указанный выше, должен выполняться только через HTTPS.
MR-SK
3

Я считаю подход:

  1. Первый запрос, клиент отправляет идентификатор / код доступа
  2. Обменять id / pass на уникальный токен
  3. Проверять токен при каждом последующем запросе до истечения срока его действия

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

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

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

Dynrepsys
источник
3

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

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

В зависимости от того, какой веб-сервер вы используете, должен быть способ указать аутентификацию клиента, который будет принимать сертификат клиента, но не требует его. Например, в Tomcat при указании коннектора https вы можете установить clientAuth = want вместо true или false. Затем вы должны обязательно добавить свой самозаверяющий сертификат CA в свое хранилище доверенных сертификатов (по умолчанию файл cacerts в JRE, который вы используете, если вы не указали другой файл в конфигурации своего веб-сервера), поэтому единственными доверенными сертификатами будут сертификаты, выпущенные из ваш самоподписанный CA.

На стороне сервера вы разрешаете доступ к службам, которые хотите защитить, только если вы можете получить сертификат клиента из запроса (не null), и проходите любые проверки DN, если вы предпочитаете дополнительную безопасность. Пользователи без клиентских сертификатов по-прежнему смогут получить доступ к вашим услугам, но просто не будут иметь сертификатов, представленных в запросе.

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

bobz32
источник
3

5. Что-то еще - должны быть другие решения?

Вы правы, есть! И называется он JWT (JSON Web Tokens).

JSON Web Token (JWT) - это открытый стандарт (RFC 7519), который определяет компактный и автономный способ безопасной передачи информации между сторонами в виде объекта JSON. Эту информацию можно проверить и доверять, потому что она имеет цифровую подпись. JWT могут быть подписаны с использованием секрета (с помощью алгоритма HMAC) или пары открытого / закрытого ключей с использованием RSA.

Я настоятельно рекомендую изучить JWT. Это гораздо более простое решение проблемы по сравнению с альтернативными решениями.

https://jwt.io/introduction/

justin.hughey
источник
1

Вы можете создать сеанс на сервере и совместно использовать его sessionIdмежду клиентом и сервером при каждом вызове REST.

  1. Первый запрос Аутентифицировать REST: /authenticate. Возвращает ответ (в соответствии с форматом вашего клиента) с sessionId: ABCDXXXXXXXXXXXXXX;

  2. Сохраните это sessionIdв Mapреальном сеансе. Map.put(sessionid, session)или использовать SessionListenerдля создания и уничтожения ключей для вас;

    public void sessionCreated(HttpSessionEvent arg0) {
      // add session to a static Map 
    }
    
    public void sessionDestroyed(HttpSessionEvent arg0) {
      // Remove session from static map
    }
    
  3. Получать sessionid с каждым вызовом REST, например URL?jsessionid=ABCDXXXXXXXXXXXXXX(или другим способом);

  4. Получить HttpSessionс карты, используя sessionId;
  5. Подтвердить запрос для этого сеанса, если сеанс активен;
  6. Отправьте ответ или сообщение об ошибке.
arviarya
источник
0

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

Девин М
источник
Хм, я не уверен, что смог понять объяснение. О каком пользователе мы говорим? Я говорю о приложении, взаимодействующем с другим приложением. Я полагаю, вы это поняли, но я все еще не могу понять. Что происходит, например, когда истекает срок действия этого «токена»?
Tommi
Токен, который вы генерируете и отправляете обратно в другое приложение, является постоянным токеном, он привязан к пользователю и к приложению. Вот ссылка на документы foursquares developer.foursquare.com/docs/oauth.html, и это в основном oauth2, так что ищите хорошее решение для аутентификации.
Devin M
Похоже, это недоразумение - за клиентской службой вообще нет конечного пользователя . В документации Foursquare, на которую вы ссылаетесь, вкратце упоминается доступ без пользователя, так что это было, по крайней мере, несколько полезно - спасибо! Но я до сих пор не могу составить полного представления о том, как это будет работать на самом деле.
Tommi
Тогда просто сгенерируйте ключи для приложений, если все, что вы делаете, это разрешаете доступ приложениям, тогда для аутентификации должны работать простые application_id и application_key. Если вы хотите, чтобы они аутентифицировались с помощью токена, изучите возможность использования параметров аутентификации токена в devise, поскольку это будет просто параметр, передаваемый с запросом URL-адреса в ваше приложение.
Devin M
Но разве тогда это не то же самое, что и сценарий токена аутентификации имя пользователя + пароль = сеанс? Разве application_id и application_key не являются синонимами имени пользователя и пароля? :) Совершенно нормально, если это действительно стандартная практика для такой ситуации - как я уже сказал, у меня нет опыта в этом - но я просто подумал, что могут быть другие варианты ...
Томми
-3

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

Даган
источник
Между конечным пользователем и серверной службой? Разве это не оставит клиентские службы вообще без аутентификации? Это не то, что я хочу. Я, конечно, могу разместить этот средний уровень между моей веб-службой и клиентскими службами, но при этом остается открытым вопрос: какой будет фактическая модель аутентификации?
Tommi
Средним уровнем может быть веб-сервер, такой как Nginx, там вы можете выполнять аутентификацию. Модель аутентификации может быть основана на сеансе.
Даганг
Как я пытался объяснить в своем вопросе, мне не нужна схема аутентификации на основе сеанса для этих клиентских служб. (См. Мои обновления по вопросу.)
Томми
Я предлагаю вам использовать белый список IP-адресов или диапазон IP-адресов вместо имени и пароля. Обычно ip службы клиента стабильный.
Даганг