/ OAuth

[PHP] Flickr OAuth PHP 認證小筆記 Part 2

俗話說得好,出來跑得總是要還。所以,由於昨天 OAuth 一直鬼打牆,所以我參考了 Plruk API 的開發工具,並且 Plurk 有很佛心的提供了 OAuth 測試平台,最起碼這樣我可以驗證我的簽名是否正確。所以,我就一個步驟一個步驟去測試這件事情了。然而,最後我發現一件事情,如果你使用POST的方法來傳遞request_token所需要的資料時,Yahoo! 那邊會失敗(原因不明)。


天殺地就是那個 POST 讓我鬼打牆一天啊!

雖然說 flickr 的 OAuth 說明中,有範例是使用 GET 的方式去提取,**但是!他沒跟你講說使用 cURL 所要傳入的CURLOPT_URL是不需要帶入 params 的!**是的,你沒看錯,我就是因為想說使用 cURL 用 GET 提取,所以很雞婆的把完整的網址列帶入CRULOPT_URL裡面,然後就永遠簽名失敗(翻桌(該死的 flickr 還我青春來啊!!!!

這是 flickr 官方提供的範例:

http://www.flickr.com/services/oauth/request_token > ?oauth_nonce=95613465 > &oauth_timestamp=1305586162 > &oauth_consumer_key=653e7a6ecc1d528c516cc8f92cf98611 > &oauth_signature_method=HMAC-SHA1 > &oauth_version=1.0 > &oauth_signature=7w18YS2bONDPL%2FzgyzP5XTr5af4%3D > &oauth_callback=http%3A%2F%2Fwww.example.com

請不要再相信沒有根據的說法了!

正確完整的 cURL 請依照下列步驟:

<?php

define("FLICKR_CONSUMER_KEY", "/* Fill your flickr api key here. */");
define("FLICKR_CONSUMER_SECRET", "/* Fill your flickr api secret here.*/");

define("FLICKR_OAUTH_HOST", "http://www.flickr.com/services");
define("FLICKR_REQUEST_TOKEN_URL", FLICKR_OAUTH_HOST . "/oauth/request_token");
define("FLICKR_AUTHORIZE_URL", FLICKR_OAUTH_HOST . "/oauth/authorize");
define("FLICKR_ACCESS_TOKEN_URL", FLICKR_OAUTH_HOST . "/oauth/access_token");

if(session_id() == "") session_start();

$params = array(
    'oauth_callback' => 'http://example.com/',
    'oauth_consumer_key' => FLICKR_CONSUMER_KEY,
    'oauth_nonce' => crc32(time()),
    'oauth_signature_method' => 'HMAC-SHA1',
    'oauth_timestamp' => time(),
    'oauth_version' => '1.0',
);

$key = urlencode(FLICKR_CONSUMER_SECRET).'&';
$base_string = "GET&".urlencode(FLICKR_REQUEST_TOKEN_URL).'&'.urlencode(http_build_query($params));
$signature = base64_encode(hash_hmac("sha1", $base_string, $key, true));

$params['oauth_signature'] = urlencode($signature);

$h   = array();
$h[] = 'Authorization: OAuth realm=""';
foreach ($params as $name => $value) {
    if (strncmp($name, 'oauth_', 6) == 0 || strncmp($name, 'xoauth_', 7) == 0) {
        $h[] = $name.'="'.$value.'"';
    }
}
$hs = implode(', ', $h);
$header[] = $hs;

$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER,  $header);
curl_setopt($ch, CURLOPT_URL,             FLICKR_REQUEST_TOKEN_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER,          true);
curl_setopt($ch, CURLOPT_TIMEOUT,         30);

$txt = curl_exec($ch);

var_dump($txt);

以上的code請複製貼上,不要忘了改,執行的結果會類似這個:

string(842) "HTTP/1.1 200 OK
Date: Thu, 15 Dec 2011 02:23:11 GMT
P3P: policyref="http://p3p.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV"
Set-Cookie: localization=en-us%3Bxx%3Btw; expires=Thu, 12-Dec-2013 02:23:11 GMT; path=/; domain=.flickr.com
Cache-Control: private
X-Served-By: www207.flickr.mud.yahoo.com
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8
Age: 1
Via: HTTP/1.1 r16.ycpi.ne1.yahoo.net (YahooTrafficServer/1.20.9 [cMsSf ]), HTTP/1.1 r19.ycpi.tw1.yahoo.net (YahooTrafficServer/1.20.9 [cMsSf ])
Server: YTS/1.20.9
Transfer-Encoding: chunked
Connection: keep-alive

oauth_callback_confirmed=true&oauth_token=72157628424170661-3d6781db1de9ebf4&oauth_token_secret=07e0182c05554bcc"

這樣就是取得一個暫時的token來給後續的認證使用。當我取得這兩個值之後,我可以先用oauth_token取得使用者的授權認證,當使用者取得之後,會依照oauth_callback來返回到你的網站。當使用者返回到你的網站的時候,網址會帶有oauth_tokenoauth_verifier兩個數值,這兩個數值就是下一步access_token所需要用到的東西。

oauth_token_secret要拿來幹麼?

請注意,剛剛的範例原始碼!

$key = urlencode(FLICKR_CONSUMER_SECRET).'&';

這是取得 HMAC-SHA1 所需要的鍵值,當我拿到oauth_token_secret的時候,這個鍵值就會改變。千萬不要傻傻的一直使用這個鍵值去做簽名,這樣永遠不會通過的。這個時候鍵值的產生需要做一點改變。

$key = urlencode(FLICKR_CONSUMER_SECRET).'&'.urlencode($OAUTH_TOKEN_SECRET); 

是的,你必須自己把oauth_token_secret取出後,附加在原有簽名用的鍵值上面,這樣做出來的簽名才會是正確的。所以,我們來看看authorizationaccess_token要怎麼做。

<?php

// 上面省略一萬行
$txt = curl_exec($ch);

if(preg_match_all("/(oauth_callback_confirmed|oauth_token|oauth_token_secret)=([a-z0-9\-]*)/i", $txt, $m)) {
    /*
     * 簡單的正規把三個東西拿出來。
     * $m[1][0] => "oauth_callback_confirmed"
     * $m[1][1] => "oauth_token"
     * $m[1][2] => "oauth_token_secret"
     * $m[2][0] => // 這裡是 oauth_callback_confirmed 的值
     * $m[2][1] => // 這裡是 oauth_token 的值
     * $m[2][2] => // 這裡是 oauth_token_secret 的值
     */

    $_SESSION['oauth_token_secret'] = $m[2][2];
    header("Location: ".FLICKR_AUTHORIZE_URL."?oauth_token=".$m[2][1]."&perms=read");
} else {
    // 錯了喔~
}

以上就是把我拿到的oauth_token轉回去給 Flickr 做應用程式認證動作,上面是取得read的權限,關於 Flickr 的權限,官方文件可能不好找,不過他有四種:none, read, write, delete 可以使用。然後當 Flickr 認證結束之後,他就會依照你當初request_token所設定的oauth_callback網址來返回。所以,你必須要有一個地方來接收返回的資訊。

<?php

define("FLICKR_CONSUMER_KEY", "/* Fill your flickr api key here. */");
define("FLICKR_CONSUMER_SECRET", "/* Fill your flickr api secret here.*/");

define("FLICKR_OAUTH_HOST", "http://www.flickr.com/services");
define("FLICKR_REQUEST_TOKEN_URL", FLICKR_OAUTH_HOST . "/oauth/request_token");
define("FLICKR_AUTHORIZE_URL", FLICKR_OAUTH_HOST . "/oauth/authorize");
define("FLICKR_ACCESS_TOKEN_URL", FLICKR_OAUTH_HOST . "/oauth/access_token");

if(session_id() == "") session_start();

if(isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) {

    $oauth_token = $_GET['oauth_token'];
    $oauth_verifier = $_GET['oauth_verifier'];

    $params = array(
        'oauth_callback' => 'http://example.com/',
        'oauth_consumer_key' => FLICKR_CONSUMER_KEY,
        'oauth_nonce' => crc32(time()),
        'oauth_signature_method' => 'HMAC-SHA1',
        'oauth_timestamp' => time(),
        'oauth_token' => $oauth_token,
        'oauth_verifier' => $oauth_verifier,
        'oauth_version' => '1.0',
    );

    $key = urlencode(FLICKR_CONSUMER_SECRET).'&'.urlencode($_SESSION['oauth_token_secret']);
    $base_string = "GET&".urlencode(FLICKR_ACCESS_TOKEN_URL).'&'.urlencode(http_build_query($params));

    $signature = base64_encode(hash_hmac("sha1", $base_string, $key, true));

    $params['oauth_signature'] = urlencode($signature);

    $h   = array();
    $h[] = 'Authorization: OAuth realm=""';
    foreach ($params as $name => $value) {
        if (strncmp($name, 'oauth_', 6) == 0 || strncmp($name, 'xoauth_', 7) == 0) {
            $h[] = $name.'="'.$value.'"';
        }
    }
    $hs = implode(', ', $h);
    $header[] = $hs;

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_HTTPHEADER,  $header);
    curl_setopt($ch, CURLOPT_URL,             FLICKR_ACCESS_TOKEN_URL);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER,          false);
    curl_setopt($ch, CURLOPT_TIMEOUT,         30);

    $txt = curl_exec($ch);

    var_dump($txt);

    exit;
} else {
    echo '錯了喔~';
}

然後最後我們拿到access_token的結果(底下的 token 是假的是假的是假的是假的是假的是假的是假的是假的是假的)。

string(165) "fullname=Cain%20Chen&oauth_token=10200228385775493-e33b2dd4d7781907&oauth_token_secret=1fbc526fc50201fc&user_nsid=69875871%40N00&username=%E8%A9%B2%E9%9A%B1%E4%BC%AF"

需要注意的點!

請注意$params這個陣列裡面的順序,在 OAuth 的規定裡面,鍵值順序需要依照字母順序排序,所以,一旦你的順序不對,你的簽名檔永遠都會不對,所以請留意簽名的部份。

然後,由於 OAuth 需要送一組 header,所以裡面有一段是關於 header 的處理。

$h   = array();
$h[] = 'Authorization: OAuth realm=""';
foreach ($params as $name => $value) {
    if (strncmp($name, 'oauth_', 6) == 0 || strncmp($name, 'xoauth_', 7) == 0) {
        $h[] = $name.'="'.$value.'"';
    }
}
$hs = implode(', ', $h);
$header[] = $hs;

他會把 header 拼成一個字串,大概會像是這個樣子:

string(288) "Authorization: OAuth realm="", oauth_callback="http://example.com/", oauth_consumer_key="75f8a673fd36a48b22b67cee71a818af", oauth_nonce="1400643998", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1323916563", oauth_version="1.0", oauth_signature="n9ScNexrOSFtLFlWimu6RrrmKMw%3D""

當你拿到最後的access_token之後,接下來就可以做壞事了。