忽如一夜春風來,千萬開放平台開。然而開放平台不是開房平台,你需要先申請一個叫access_token的東東。這個東西就是你在開放平台上的應用(consumer)、開放平台、使用你的應用來訪問自身資料客戶的一個三方契約;契約寫明本使用者允許consumer訪問本使用者在開房平台上的資料;consumer每次拿著這個契約給開房平台看一下,就可以大搖大擺的取得想要的資料了。
取得access token常用的有兩種方法:oauth1.a和oauth2.0(本來存在oauth1.0,後來因為安全性問題被棄用)。流程開始之前,你必須為你的應用申請一個consumer_key(任意申請key和token都會附帶secret,secret在2.0裡面基本沒啥用),consumer_key用於申請,consumer_secret+當前其他token附帶的secret用於提取傳輸參數的簽名,校正正確性。
oauth1.a是一種比較繁瑣的認證流程。它要先使用consumer_key申請一個未授權的request token,使用者授權之後變為授權的request token並產生一個verified以get方式送到應用的回調地址(無回調地址即ood,會發送一個pin code到瀏覽器),然後應用將發送授權的request_token(事實上和未授權的一樣,因此上步不存在新的secret)+verified申請access_token.
之後調用api時需要將access token+函數參數+(consumer_secret+access_secret)提取簽名發送到開房平台,開放平台將函數結果返回。
oauth2一種存在4中方式申請,採用了https加密,因此不用個人手動提取簽名,且認證步驟簡化,所以說簡單很多。另外,其返回結果通常是以json格式返回。在此就不一一解釋了。
Http請求資料可以有多種方式,在開放平台中接受三種: post, get, head. post將資料放在http請求體中,get將請求資料放在網址中和網址以?分割,head不是head request是把請求放在head中,實驗表明其方式是get. 具體實驗寫在後面。由於get是將所有資料以近乎於明文的方式拼接到網址,因此post,head相對安全點,所以在發送某些敏感性資料時推薦使用post。
============================================煩人的理論到此為止=============================================================
在oauth1.a&2.0申請token過程中,需要發送多次http請求,有些需要請求需要瀏覽器重新導向。所以先封裝各種不同的用於發送的http請求的類,使用了一個工廠,方便調用不同的請求方式。
sender.php
<?phpabstract class Sender{ public abstract function send($url, $data);}abstract class GetSender extends Sender{ protected function makeUrl($aUrl, $data){ $url=$aUrl.'?'; foreach($data as $key=>$value){ if($url.chr(strlen($url)-1)!='?'){ $url.='&'; } $url=$url.$key.'='.$value; } return $url; }}abstract class PostSender extends Sender{}abstract class HeadSender extends Sender{ protected function makeHead($data){ $head = array(); if(array_key_exists('oauth_version', $data) && $data['oauth_version']=='1.0'){ $head[0] = 'Authorization: OAuth '; }else{ $head[0] = 'Authorization: OAuth2 '; } foreach($data as $key=>$value){ $head[0] = $head[0].$key.'='.$value.','; } $head[0][strlen($head[0])-1]=null; return $head; }}class RedirectGetSender extends GetSender{ public function send($url, $data){ $url = $this->makeUrl($url, $data); header("Location:$url"); }}class DirectGetSender extends GetSender{ public function send($url, $data){ $url = $this->makeUrl($url, $data); $result = file_get_contents($url); return $result; }}class RedirectPostSender extends PostSender{ public function send($url, $data){ echo "click submit to authorize!<br>"; echo '<form action="'.$url.'" method="post">'; foreach($data as $key=>$value){ echo '<input type="hidden" name='.$key.' value="'.$value.'"/>'; } echo '<input type="submit" value="SBUMIT">'; echo '</form>'; }}class DirectPostSender extends PostSender{ public function send($url, $data){ $curlHandle = curl_init(); curl_setopt($curlHandle, CURLOPT_URL, $url); curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true); curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array('Expect:')); curl_setopt($curlHandle, CURLOPT_POST, true); curl_setopt($curlHandle, CURLOPT_POSTFIELDS, http_build_query($data)); $result = curl_exec($curlHandle); curl_close($curlHandle); return $result; }}class DirectHeadSender extends HeadSender{ public function send($url, $data){ $head = $this->makeHead($data); $curlHandle = curl_init(); curl_setopt($curlHandle, CURLOPT_URL, $url); curl_setopt($curlHandle, CURLOPT_HEADER, false); curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $head); curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curlHandle, CURLOPT_FRESH_CONNECT, 1); $result = curl_exec($curlHandle); curl_close($curlHandle); return $result; }}class RedirectHeadsender extends HeadSender{ public function send($url, $data){ echo "There are no head method for this redirect request!<br>"; }}class SenderFactory{ public static function createSender($isRedirect, $mode){ switch (strtolower($mode)){ case "get": if($isRedirect===true){ return new RedirectGetSender(); }else{ return new DirectGetSender(); } break; case "post": if($isRedirect===true){ return new RedirectPostSender(); }else{ return new DirectPostSender(); } break; case "head": if($isRedirect ===true){ return new RedirectHeadSender(); }else{ return new DirectHeadSender(); } break; } }}?>
--------------------------------------oauth1.a----------------------------------------------------------
url->base string類;簽名類:
url.php
<?phpclass Helper_URL{ /** * * Build HTTP Query * * * * @param array $params Name => value array of * parameters * * * * @return string HTTP query * */ public static function buildHttpQuery(array $params) { if (empty($params)) { return ''; } $keys = self::urlEncode(array_keys($params)); $values = self::urlEncode(array_values($params)); $params = array_combine($keys, $values); uksort($params, 'strcmp'); $pairs = array(); foreach ($params as $key => $value) { $pairs[] = $key . '=' . $value; } return implode('&', $pairs); } /** * * URL Encode * * * * @param mixed $item string or array of items * to url encode; * * * * @return mixed url encoded string * or array of strings * */ public static function urlEncode($item) { static $search = array('%7E'); static $replace = array('~'); if (is_array($item)) { return array_map(array(__CLASS__, 'urlEncode'), $item); } if (is_scalar($item) === false) { return $item; } return str_replace($search, $replace, rawurlencode($item)); } /** * * URL Decode * * * * @param mixed $item Item to url decode * * * * @return string URL decoded string * */ public static function urlDecode($item) { if (is_array($item)) { return array_map(array(__CLASS__, 'urlDecode'), $item); } return rawurldecode($item); }}?>
signature.php
<?phpinclude_once('url.php');/* * This class is used to generate the signature of base string. * $url is the url your request will be sended to. and if in 'get' mode it is * only the part before '?'; * $reqMod is request mode: 'get', 'post'; when paremeters are put in the head, * its request mode is 'get'; * $params are all the params except 'oauth_signature'; * $consumerSecret is your consumer_secret; * $token_secret is the secret you got in the nearest formal step; */class Signaturer{ public function getSignature($url, $reqMode, array $params, $consumerSecret, $tokenSecret=''){ $baseStr = $this->getBaseStr($reqMode, $url, $params); if(isSet($params['oauth_signature_method'])){ $signMethod = $this->getSignMethod($params['oauth_signature_method']); }else{ $signMethod = 'sha1'; } $key = $this->getKey($consumerSecret, $tokenSecret); return base64_encode(hash_hmac($signMethod, $baseStr, $key, true)); } /* used for constructing the base string. * */ public function getBaseStr($method, $url, $params){ if (array_key_exists('oauth_signature', $params)) { unset($params['oauth_signature']); } $urlParts = explode('?', $url); $parts = array(strtoupper($method), $urlParts[0], Helper_URL::buildHTTPQuery($params)); return implode('&', Helper_URL::urlEncode($parts)); } /* * used to analyse signature method automatically * */ public function getSignMethod($signMethod){ list($begin, $end) = explode('-', $signMethod); return strtolower($end); } /* * create key for signature; * in the first step, since there is no secret, it is "$consumerSecret&" * */ public function getKey($consumerSecret, $tokenSecret = ''){ $secrets = array($consumerSecret, $tokenSecret); return implode('&', Helper_URL::urlEncode($secrets)); }}?>
oauthTokenFetcher.php
<?phpinclude_once('sender.php');include_once('signaturer.php');/* * to apply an access_token, there are three step: * 1. consumer (get post head) a request for unauthorised_request_token and an * unauthorizised request token would be sended back; * 2. consumer redirect to the authorization center url; * 3. consumer get the authorized request token in the callback url; and then * consumer (get post head) a request for access token; finally, an access * token is sended back; */class OauthTokenFetcher{ private $signaturer;function __construct(){$this->signaturer = new Signaturer();}public function setNonceStamp(array &$params){$params['oauth_nonce'] = md5(mt_rand());$params['oauth_timestamp'] = time();} public function fetchToken($url, $reqMode, array $params, $consumerSecret, $tokenSecret, $tokenStyle='request_token'){ $reqParam = strtolower($reqMode)==='head'?'get':$reqMode;$params['oauth_signature'] = $this->signaturer->getSignature($url, $reqParam, $params, $consumerSecret, $tokenSecret); foreach($params as $key=>$value){ $params[$key] = Helper_URL::urlEncode($value); } if($tokenStyle === 'request_token'){ $sender = SenderFactory::createSender(true, $reqMode); $sender->send($url, $params); }else{ $sender = SenderFactory::createSender(false, $reqMode); $result = $sender->send($url, $params); return $result; } } /* * parse return of fetchToken * */ public function parse($str){$infoTmps = explode('&', $str);$info = array();foreach($infoTmps as $value){list($begin, $end) = explode('=', $value);$info[$begin] = $end;}return $info;}}?>
--待續--