Подтвердить что ты не робот

Как реализовать FosOAuthServerBundle для защиты REST API?

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

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

Итак, я попытался понять, насколько я мог этот пример реализации (единственный, который я нашел), но есть вещи, которые я до сих пор Не понимаю.

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

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

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

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

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


ИЗМЕНИТЬ после ответа @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 с маркером как значением. Кажется, это работает. К сожалению, мне приходится делать обратное вспять на код связки, чтобы найти это вместо того, чтобы читать его в документации.

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

4b9b3361

Ответ 1

Я также обнаружил, что документация может быть немного запутанной. Но после многих часов попыток я понял это с помощью этого блога (обновление - в блоге нет дольше). В вашем случае вам не нужна запись брандмауэра для ^/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, client, auth_code, refresh_token), должны иметь больше полей, чем то, что показано в документах...

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

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

Таблица кода Auth: id (int), client_id (int), user_id (int)

Обновить таблицу токенов: id (int), client_id (int), 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 и secret, выполните эту команду из командной строки (это введет в базу данных необходимые идентификаторы и прочее):

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

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

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

Я также использую классическую аутентификацию 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);