如何实现FosOAuthServerBundle来保护REST API?

问题描述 投票:35回答:1

我想使用FOSOAuthServerBundle提供一个使用OAuth2保护的RESTful API,我不确定我该做什么。

我遵循基本步骤from the documentation,但有些事情缺失,我找不到我需要的完整例子。

所以,我试图了解最好的this example of implementation(我发现的唯一一个),但仍然有一些我不明白的事情。

首先,为什么我们需要API中的登录页面?假设我的客户端是iPhone或Android应用程序,我看到应用程序上登录页面的兴趣,但我认为客户端只需从API调用Web服务来获取其令牌,我错了吗?那么如何通过REST端点实现自动化和令牌提供?

然后,文档告诉写这个防火墙:

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

我不知道如何添加身份验证过程。我应该写自己的一个,例如跟随this tutorial还是我完全错了?

在全球范围内,有人可以花时间在文档中的五个步骤之后解释所需的过程,以提供OAuth2安全的RESTful API吗?这将是非常好的......


在@Sehael回答之后编辑:

在它完美之前我还有一些问题......

这里的“客户”代表什么?对于exemaple,我应该为iPhone应用程序创建一个客户端,为Android应用程序创建另一个客户端吗?我是否必须为每个想要使用API​​的实例创建一个新客户端?在这种情况下,最佳做法是什么?

与您不同,我不会将OAuth流程用于前端网站,而是使用“经典”symfony方式。你觉得这很奇怪,还是正常的?

refresh_token的用处是什么?如何使用它?

我试图测试我的新OAuth受保护服务。我使用支持OAuth 1.0的POSTman chrome扩展,OAuth2看起来像OAuth1足以用POSTman进行测试吗?有一个“秘密令牌”字段,我不知道如何填写。如果我不能,我很高兴看到你的(@Sehael)PHP课程,就像你提出的那样。 /编辑:好的我觉得我找到了这个答案。我刚刚将access_token添加为GET参数,并将令牌作为值。它似乎工作。不幸的是,我必须对bundle代码进行反向engenering才能找到它而不是在文档中读取它。

无论如何,非常感谢!

symfony oauth-2.0 fosoauthserverbundle
1个回答
53
投票

我还发现文档可能有点令人困惑。但经过几个小时的尝试,我在this blog的帮助下弄明白了(更新 - 博客不再存在,改为Internet Archive)。在您的情况下,您不需要^/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(字符串),范围(字符串),expires_at(int)

客户端表:id(int),random_id(字符串),secret(字符串),redirect_urls(字符串),allowed_grant_types(字符串)

验证代码表:id(int),client_id(int),user_id(int)

刷新令牌表:id(int),client_id(int),user_id(int),token(字符串),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,从命令行执行此命令(这将在数据库中插入必要的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和secret后,您就可以进行身份​​验证了。在您的浏览器中提出如下所示的请求:

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类来使用oAuth来调用我的Symfony REST api,如果你认为这样有用,请告诉我,我可以传递它。

UPDATE

回答您的进一步问题:

“客户”在同一博客上描述,只是另一篇文章。在这里阅读Clients and Scopes部分,它应该为您澄清客户是什么。就像文章中提到的那样,每个用户都不需要客户端。如果需要,您可以为所有用户设置一个客户端。

我实际上也在为我的前端站点使用经典的Symfony身份验证,但这可能在将来发生变化。因此,将这些事情放在脑海中总是好的,但我不会说将这两种方法结合起来很奇怪。

当access_token已过期且您想要请求新的access_token而不重新发送用户凭据时,将使用refresh_token。相反,您发送刷新令牌并获得新的access_token。这对于REST API来说并不是必需的,因为单个请求可能不会花费足够长的时间来使access_token过期。

oAuth1和oAuth2是非常不同的,所以我认为你使用的方法不起作用,但我从来没有尝试过。但只是为了测试,只要您在GET查询字符串中传递access_token=[ACCESS_TOKEN](实际上是针对所有类型的请求),您就可以发出正常的GET或POST请求。

但无论如何,这是我的班级。我使用配置文件来存储一些变量,而我没有实现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);
© www.soinside.com 2019 - 2024. All rights reserved.