忽如一夜春风来,千万开放平台开。然而开放平台不是开房平台,你需要先申请一个叫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
<?php
abstract 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
<?php
class 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
<?php
include_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
<?php
include_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;
}
}
?>
--待续--