2014-01-21 7 views
33

Я хотел бы предоставить RESTful API, защищенный OAuth2, используя FOSOAuthServerBundle, и я не совсем уверен в том, что мне нужно делать.Как реализовать FosOAuthServerBundle для защиты REST API?

Я выполнил основные шаги from the documentation, но некоторые вещи отсутствуют, и я не могу найти полный пример того, что мне нужно.

Итак, я попытался понять лучшее, что мог, this example of implementation (единственное, что я нашел), но все еще есть вещи, которые я не понимаю.

Во-первых, зачем нам нужна страница входа в API? Предположим, что мой клиент - приложение для iPhone или Android, я вижу интерес к странице входа в приложение, но я думаю, что клиент должен просто вызвать веб-сервис из API, чтобы получить его токен, я ошибаюсь? Итак, как реализовать автоматизацию и токен, предоставляя через конечную точку REST?

Затем документация говорит, чтобы написать этот брандмауэр:

oauth_authorize: 
    pattern: ^/oauth/v2/auth 
    # Add your favorite authentication process here 

И я не знаю, как добавить процесс аутентификации. Должен ли я писать свой собственный, например, после this tutorial или я совершенно не прав?

В глобальном масштабе может потребоваться время, чтобы объяснить процесс, необходимый после пяти шагов в документах, чтобы обеспечить защищенный RESTful API OAuth2? Было бы очень приятно ...


EDIT после @Sehael ответа:

у меня еще есть несколько вопросов, прежде чем она совершенна ...

Что представляет «Клиент» здесь ? Для exemaple следует создать клиент для iPhone-приложения, а другой - для Android-приложения? и мне нужно создать новый клиент для каждого экземпляра, который хочет использовать API? Какова наилучшая практика в этом случае?

В отличие от вас, я не использую процесс OAuth для переднего веб-сайта, но «классический» способ symfony. Вам это кажется странным, или это нормально?

В чем польза refresh_token? Как это использовать?

Я попытался проверить свои новые службы защиты OAuth. Я использовал расширение chrome POSTman, которое поддерживает OAuth 1.0, делает ли OAuth2 похожим на OAuth1 достаточно для тестирования с помощью POSTman? Существует поле «секретный токен», которое я не знаю, как заполнять. Если я не могу, я был бы рад видеть ваш (@Sehael) класс PHP, как вы предложили./Edit: OK Я думаю, что нашел ответ для этого. Я только добавил access_token как параметр GET с маркером как значением. Кажется, это работает. К сожалению, мне приходится делать обратное вспять на код связки, чтобы найти это вместо того, чтобы читать его в документации.

В любом случае, спасибо большое!

ответ

51

Я также обнаружил, что документация может быть немного запутанной. Но после многих часов попыток я понял это с помощью this blog (обновление - блог больше не существует, изменен на Интернет-архив). В вашем случае вам не нужна запись брандмауэра для ^/oauth/v2/auth, потому что это для страницы авторизации. Вы должны помнить, что oAuth может сделать ... он используется не только для REST api. Но если REST api - это то, что вы хотите защитить, вам это не нужно.вот пример конфигурации брандмауэра из моего приложения:

firewalls: 

    oauth_token: 
     pattern: ^/oauth/v2/token 
     security: false 

    api_firewall: 
     pattern: ^/api/.* 
     fos_oauth: true 
     stateless: true 
     anonymous: false 

    secure_area: 
     pattern: ^/ 
     fos_oauth: true 
     form_login: 
      provider: user_provider 
      check_path: /oauth/v2/auth_login_check 
      login_path: /oauth/v2/auth_login 
     logout: 
      path: /logout 
      target:/
     anonymous: ~ 

access_control: 
    - { path: ^/oauth/v2/auth_login$, role: IS_AUTHENTICATED_ANONYMOUSLY } 
    - { path: ^/, roles: IS_AUTHENTICATED_FULLY } 

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

И в моем config.yml:

fos_oauth_server: 
    db_driver: orm 
    client_class:  BB\AuthBundle\Entity\Client 
    access_token_class: BB\AuthBundle\Entity\AccessToken 
    refresh_token_class: BB\AuthBundle\Entity\RefreshToken 
    auth_code_class:  BB\AuthBundle\Entity\AuthCode 
    service: 
     user_provider: platform.user.provider 
     options: 
      supported_scopes: user 

Следует также отметить, что таблицы, создаваемые в базе данных (access_token, клиент, AUTH_CODE, refresh_token) должны иметь больше полей, чем то, что показано в документации ...

токен доступа Таблица: идентификатор (целое), client_id (целое), user_id (INT), маркер (строка), область (строка), expires_at (интермедиат)

Таблица клиентов: идентификатор (целое), random_id (строка), секрет (строка), redirect_urls (строка), allowed_grant_types (строка)

Auth Таблица кодов: идентификатор (целое), client_id (целое), user_id (целое)

Refresh токен Таблица: идентификатор (целое), client_id (целое), user_id (INT), маркер (строка), expires_at (INT), область (строка)

Эти таблицы будут хранить информацию, необходимую для oAuth, так что обновите свои объекты Doctrine, чтобы они соответствовали таблицам db, как указано выше.

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

Создать файл на /src/My/AuthBundle/Command/CreateClientCommand.php (вам нужно будет создать папку Command) Этот код из статьи я связан выше, и показывает пример того, что вы можете поместить в этот файл:

<?php 
# src/Acme/DemoBundle/Command/CreateClientCommand.php 
namespace Acme\DemoBundle\Command; 

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 
use Symfony\Component\Console\Input\InputArgument; 
use Symfony\Component\Console\Input\InputOption; 
use Symfony\Component\Console\Input\InputInterface; 
use Symfony\Component\Console\Output\OutputInterface; 

class CreateClientCommand extends ContainerAwareCommand 
{ 
    protected function configure() 
    { 
     $this 
      ->setName('acme:oauth-server:client:create') 
      ->setDescription('Creates a new client') 
      ->addOption(
       'redirect-uri', 
       null, 
       InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 
       'Sets redirect uri for client. Use this option multiple times to set multiple redirect URIs.', 
       null 
      ) 
      ->addOption(
       'grant-type', 
       null, 
       InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 
       'Sets allowed grant type for client. Use this option multiple times to set multiple grant types..', 
       null 
      ) 
      ->setHelp(
       <<<EOT 
        The <info>%command.name%</info>command creates a new client. 

<info>php %command.full_name% [--redirect-uri=...] [--grant-type=...] name</info> 

EOT 
      ); 
    } 

    protected function execute(InputInterface $input, OutputInterface $output) 
    { 
     $clientManager = $this->getContainer()->get('fos_oauth_server.client_manager.default'); 
     $client = $clientManager->createClient(); 
     $client->setRedirectUris($input->getOption('redirect-uri')); 
     $client->setAllowedGrantTypes($input->getOption('grant-type')); 
     $clientManager->updateClient($client); 
     $output->writeln(
      sprintf(
       'Added a new client with public id <info>%s</info>, secret <info>%s</info>', 
       $client->getPublicId(), 
       $client->getSecret() 
      ) 
     ); 
    } 
} 

Тогда на самом деле создать client_id и секрет, выполните эту команду из командной строки (это введет в базу данных необходимые идентификаторы и материал):

php app/console acme:oauth-server:client:create --redirect-uri="http://clinet.local/" --grant-type="password" --grant-type="refresh_token" --grant-type="client_credentials"

уведомление о том, что acme:oauth-server:client:create может быть все, что вы на самом деле назвать свою команду в CreateClientCommand.php файл с $this->setName('acme:oauth-server:client:create').

Как только у вас есть client_id и секрет, вы готовы к аутентификации. Сделайте запрос в браузере, который что-то вроде этого:

http://example.com/oauth/v2/token?client_id=[CLIENT_ID_YOU GENERATED]&client_secret=[SECRET_YOU_GENERATED]&grant_type=password&username=[USERNAME]&password=[PASSWORD]

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

Я также написал простой PHP-класс для вызова моего Symfony REST api, используя oAuth, если вы считаете, что это было бы полезно, сообщите мне, и я могу его передать.

UPDATE

В ответ на Ваши дополнительные вопросы:

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

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

Refresh_token используется, когда срок действия access_token истек, и вы хотите запросить новый access_token без повторной отправки учетных данных пользователя. вместо этого вы отправляете токен обновления и получаете новый access_token. Это не обязательно для REST API, потому что один запрос, вероятно, не займет достаточно много времени для истечения срока действия access_token.

oAuth1 и oAuth2 очень разные, поэтому я бы предположил, что метод, который вы используете, не будет работать, но я никогда не пробовал с этим. Но только для тестирования вы можете сделать обычный запрос GET или POST, если вы передаете access_token=[ACCESS_TOKEN] в строке запроса GET (фактически для всех типов запросов).

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

class RestRequest{ 
    private $token_url; 
    private $access_token; 
    private $refresh_token; 
    private $client_id; 
    private $client_secret; 

    public function __construct(){ 
     include 'config.php'; 
     $this->client_id = $conf['client_id']; 
     $this->client_secret = $conf['client_secret']; 
     $this->token_url = $conf['token_url']; 

     $params = array(
      'client_id'=>$this->client_id, 
      'client_secret'=>$this->client_secret, 
      'username'=>$conf['rest_user'], 
      'password'=>$conf['rest_pass'], 
      'grant_type'=>'password' 
     ); 

     $result = $this->call($this->token_url, 'GET', $params); 
     $this->access_token = $result->access_token; 
     $this->refresh_token = $result->refresh_token; 
    } 

    public function getToken(){ 
     return $this->access_token; 
    } 

    public function refreshToken(){ 
     $params = array(
      'client_id'=>$this->client_id, 
      'client_secret'=>$this->client_secret, 
      'refresh_token'=>$this->refresh_token, 
      'grant_type'=>'refresh_token' 
     ); 

     $result = $this->call($this->token_url, "GET", $params); 

     $this->access_token = $result->access_token; 
     $this->refresh_token = $result->refresh_token; 

     return $this->access_token; 
    } 

    public function call($url, $method, $getParams = array(), $postParams = array()){ 
     ob_start(); 
     $curl_request = curl_init(); 

     curl_setopt($curl_request, CURLOPT_HEADER, 0); // don't include the header info in the output 
     curl_setopt($curl_request, CURLOPT_RETURNTRANSFER, 1); // don't display the output on the screen 
     $url = $url."?".http_build_query($getParams); 
     switch(strtoupper($method)){ 
      case "POST": // Set the request options for POST requests (create) 
       curl_setopt($curl_request, CURLOPT_URL, $url); // request URL 
       curl_setopt($curl_request, CURLOPT_POST, 1); // set request type to POST 
       curl_setopt($curl_request, CURLOPT_POSTFIELDS, http_build_query($postParams)); // set request params 
       break; 
      case "GET": // Set the request options for GET requests (read) 
       curl_setopt($curl_request, CURLOPT_URL, $url); // request URL and params 
       break; 
      case "PUT": // Set the request options for PUT requests (update) 
       curl_setopt($curl_request, CURLOPT_URL, $url); // request URL 
       curl_setopt($curl_request, CURLOPT_CUSTOMREQUEST, "PUT"); // set request type 
       curl_setopt($curl_request, CURLOPT_POSTFIELDS, http_build_query($postParams)); // set request params 
       break; 
      case "DELETE": 

       break; 
      default: 
       curl_setopt($curl_request, CURLOPT_URL, $url); 
       break; 
     } 

     $result = curl_exec($curl_request); // execute the request 
     if($result === false){ 
      $result = curl_error($curl_request); 
     } 
     curl_close($curl_request); 
     ob_end_flush(); 

     return json_decode($result); 
    } 
} 

И затем использовать класс, просто:

$request = new RestRequest(); 
$insertUrl = "http://example.com/api/users"; 
$postParams = array(
    "username"=>"test", 
    "is_active"=>'false', 
    "other"=>"3g12g53g5gg4g246542g542g4" 
); 
$getParams = array("access_token"=>$request->getToken()); 
$response = $request->call($insertUrl, "POST", $getParams, $postParams); 
+0

Спасибо очень очень Munch @Sehael, это действительно полезно! У меня есть мой клиент, и я могу генерировать токены пользователем. Однако я отредактировал этот вопрос, потому что у меня все еще есть последние (надеюсь) взаимодействия. – maphe

+0

Я обновил свой ответ с дальнейшим объяснением – Sehael

+0

Большое спасибо! Вы спасли меня;) – maphe

Смежные вопросы