2014-01-21 62 views
33

我想提供一個使用FOSOAuthServerBundle與OAuth2進行安全保護的RESTful API,我不確定我必須做什麼。如何實現FosOAuthServerBundle來保護REST 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答案後


編輯:

我仍然有一些問題之前,它是完美的......

什麼是「客戶端」在這裏代表?例如,我應該爲iPhone應用創建客戶端,還是爲Android應用創建另一個客戶端?我是否必須爲每個想要使用API​​的實例創建一個新的客戶端?這種情況下的最佳做法是什麼?

不像你,我不使用前的網站,但「經典」的symfony方式OAuth的過程。這看起來很奇怪嗎,還是這是正常的?

什麼refresh_token的用處?如何使用它?

我試圖測試我的新OAuth保護服務。我使用支持OAuth 1.0的POSTman Chrome擴展,OAuth2看起來像OAuth1,足以使用POSTman進行測試嗎?有一個「祕密令牌」字段,我不知道如何填寫。如果我不能,我會很高興看到你的(@Sehael)PHP類,如你所建議的。 /編輯:好的,我想我找到了這個答案。我只是添加了access_token作爲GET參數,令牌值。它似乎在工作。不幸的是,我必須對包代碼進行逆向操作來發現它,而不是在文檔中閱讀它。

無論如何,非常感謝!

回答

51

我也發現文檔可能有點混亂。但經過數小時的嘗試,我在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,客戶端,AUTH_CODE,refresh_token)都要求有比所顯示的更多的字段在文檔...

訪問令牌表: ID(INT),CLIENT_ID(INT),USER_ID(INT),令牌(串),範圍(串),expires_at(INT)

客戶端表: ID(INT),random_id(串),祕密(串),redirect_urls(串),allowed_grant_types(串)

授權碼錶: 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和祕密,從命令行執行這個命令(這將插入到數據庫中必要的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驗證,但這在將來可能會改變。所以把這些東西放在頭腦裏總是好的,但我不會說把這兩種方法結合起來是很奇怪的。

refresh_token在access_token過期並且您希望請求新的access_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); 
+0

謝謝你非常非常蒙克@Sehael,這是非常有幫助!我有我的客戶,我可以由用戶生成令牌。不過,我編輯了這個問題,因爲我仍然有最後一個(我希望)interogations。 – maphe

+0

我更新了我的答案並提供了進一步的解釋 – Sehael

+0

非常感謝!你救了我;) – maphe