web-dev-qa-db-ja.com

FosOAuthServerBundleを実装して、REST API?

FOSOAuthServerBundleを使用してOAuth2で保護されたRESTful APIを提供したいのですが、どうすればよいのかわかりません。

私は基本的な手順に従いました ドキュメントから ですが、いくつかのものが欠けていて、必要なものの完全な例を見つけることができません。

それで、私はできる限りベストを理解しようとしました この実装例 (私が見つけた唯一のもの)、しかし私にはまだ理解していないことがあります。

まず、なぜAPIにログインページが必要なのですか?私のクライアントがiPhoneまたはAndroidアプリであると仮定すると、アプリのログインページの関心が表示されますが、クライアントはAPIからWebサービスを呼び出してトークンを取得する必要があると思います、私は間違っていますか?では、RESTエンドポイントを介して自動更新とトークン提供を実装する方法は?

次に、ドキュメントでこのファイアウォールを記述するように指示されています。

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

また、認証プロセスを追加する方法がわかりません。たとえば、 このチュートリアル のように自分で作成する必要がありますか、それとも完全に間違っていますか?

グローバルに、ドキュメントの5つのステップの後、OAuth2で保護されたRESTful APIを提供するために、誰かが必要なプロセスを説明するのに時間をかけることができますか?とてもいいだろう...


@Sehaelの回答の後に編集:

完璧になる前にまだ質問があります...

ここで「クライアント」とは何を表していますか?たとえば、iPhoneアプリ用のクライアントと、Androidアプリ用のクライアントを作成する必要がありますか?APIを使用するすべてのインスタンスに対して新しいクライアントを作成する必要がありますか?この場合のベストプラクティスは?

あなたとは異なり、フロントWebサイトではOAuthプロセスを使用しませんが、「古典的な」symfonyの方法を使用します。それは奇妙に思えますか、それとも普通ですか?

Refresh_tokenの有用性は何ですか?どうやって使うのですか?

私は新しいOAuth保護されたサービスをテストしようとしました。POSTmanchrome拡張機能を使用しました。これはOAuth 1.0 POSTmanでテストするのに十分なOAuth1のように見えますか?埋める方法がわからない「秘密トークン」フィールドがあります。できなければ、(@ Sehael)PHPクラス、あなたが提案したように。/編集:OKこれに対する答えを見つけたと思います。トークンとして値を持つGETパラメータとしてaccess_tokenを追加しました。動作しているようです。残念ながら、ドキュメントで読むのではなく、バンドルコードをリバースエンジニアリングして、それを見つける必要があります。

とにかく、どうもありがとう!

36
maphe

また、ドキュメントが少し混乱する可能性があることもわかりました。しかし、何時間も試してみた後、 このブログ (更新-ブログはもはや存在せず、インターネットアーカイブに変更されました)の助けを借りてそれを見つけました。あなたの場合、これは承認ページ用であるため、_^/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)、token(string)、scope(string)、expires_at(int )

クライアントテーブル:id(int)、random_id(string)、secret(string)、redirect_urls(string)、allowed_grant_types(string)

認証コードテーブル:id(int)、client_id(int)、user_id(int)

トークンテーブルの更新:id(int)、client_id(int)、user_id(int)、token(string)、expires_at(int)、scope(string )

これらのテーブルには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とシークレットを作成するには、コマンドラインから次のコマンドを実行します(これにより、必要な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_は、$this->setName('acme:oauth-server:client:create')を使用して_CreateClientCommand.php_ファイルで実際にコマンドに名前を付けることができます。

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]_

うまくいけばあなたのために働く。設定するのは間違いなくたくさんあります。一度に1ステップずつ実行してみてください。

また、oAuthを使用してSymfony PHP apiを呼び出す簡単なRESTクラスを作成しました。これが便利だと思われる場合はお知らせください。

[〜#〜] update [〜#〜]

さらなる質問への回答として:

「クライアント」は同じブログで説明されていますが、異なる記事です。ここで Clients and Scopes セクションを読んでください。クライアントとは何かを明確にする必要があります。記事で述べたように、すべてのユーザーにクライアントが必要なわけではありません。必要に応じて、すべてのユーザーに対して単一のクライアントを使用できます。

実際、フロントエンドサイトには従来のSymfony認証も使用していますが、将来変更される可能性があります。したがって、これらのことを心の奥に置いておくことは常に良いことですが、2つの方法を組み合わせることは奇妙なことではありません。

Refresh_tokenは、access_tokenの有効期限が切れており、ユーザー資格情報を再送信せずに新しいaccess_tokenを要求する場合に使用されます。代わりに、更新トークンを送信し、新しいaccess_tokenを取得します。 REST AP​​Iの場合、access_tokenの有効期限が切れるまでに単一のリクエストで十分な時間がかかることはないため、これは実際には必要ありません。

oAuth1とoAuth2は非常に異なるため、使用する方法は機能しないと思いますが、私はそれを試したことはありません。ただし、テストのために、GETクエリ文字列で_access_token=[ACCESS_TOKEN]_を渡す限り、通常のGETまたはPOSTリクエストを作成できます(実際にはすべてのタイプのリクエストに対して)。

とにかく、ここに私のクラスがあります。構成ファイルを使用していくつかの変数を保存し、削除する機能を実装していませんでしたが、それほど難しくはありません。

_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);
_
53
Sehael