OAuth1.0实践之foursquare客户端同步到饭否

上回说完了《OAuth2.0实践之foursquare客户端登录》这回还是要回来说OAuth1.0。

还是我写的那个foursquare的web客户端程序。其中加上了同步到饭否的功能,原来用的是BasicAuth,不过饭否宣布从今年元旦开始关闭BasicAuth,全面改用OAuth1.0。于是我又不得不赶在截止前几天把这部分程序改写了——还好不像上次那样完全重写,改起来还是很快的。不过其中因为OAuth1.0的签名部分影响到了几个基础函数,改了一些地方,弄出了些坏味道。因为反正用php就是为了quick and dirty,也就懒得去重构什么的了,能用就行。

同样,这里也不讨论程序的其它部分,只谈一下同步到饭否的部分。

按照标准的OAuth1.0登录流程应该是这样的:

1、在服务提供方注册一个客户端应用,取得一对consumer_key和consumer_secret;

2、通过调用提供方的oauth request_token取得一个临时token;

3、凭这个临时token和回调地址转向到提供方的authorize页面,用户在这个页面上决定是否授权;

4、用户授权后,提供方转向到回调地址,并提供一个verifier码;

5、凭这个verifier码和临时token调用提供方的oauth access_token取得正式的access_token;

6、使用access_token调用提供方的API。

以上全部调用请求中都必须附上consumer_key/secret,并且加以数字签名。

很明显,比OAuth2.0要复杂很多,还好这个技术已经比较成熟,有很多现成的库可以用。因为我的虚拟主机目前还是只支持PHP4,所以用的是一个可以支持PHP4的库:lib_oauth.php。那个流行的OAuth.php库其实稍微改改也可以,我原来也试过,不过还是觉得用现成的方便些,就换成这个lib_oauth了。

顺便吐槽一句,国内这些网站号称提供API的,个个实现都有妖蛾子,并不像国外网站做得那么规范。尤其以新浪和腾迅为甚,网易和搜狐略好,不过搜狐也有问题,OAuth1.0不支持Head方式,而且对参数也有一些特别的要求。至于饭否,它最妖的地方就是:居然没有 verifier 码!估计是通过时间和IP之类的来鉴定的吧。

好吧,具体到这个应用上来。我需要实现的功能包括两块:一是登录饭否,二是通过饭否API发消息。

首先是按饭否官方方式申请一对key/secret。

然后用类似foursquare登录部分的方式实现一个饭否登录:

用户点一个链接,程序调用request_token并重定向到饭否的authorize,用户确认授权以后回调回来,程序再调用access_token取得正式的access_token(注意:这里没有verifier)并回以保存。

最后,在调用饭否API前用OAuth1.0规范进行请求签名。

具体的登录代码如下:

function fanfou_login() {
 session_start();
 $GLOBALS['user']['fantype'] = 'oauth';

 if ($oauth_token = $_GET['oauth_token']) {  //  来自饭否的回调
  $response = fanfou_process(FANFOU_OAUTH.'access_token');  //  直接申请access_token,与标准不同的是没有verifier
  parse_str($response, $token);
  $GLOBALS['user']['fanpass'] = $token['oauth_token'] .'|'.$token['oauth_token_secret'];
  unset($_SESSION['oauth_request_token_secret']);
  $user = fanfou_process(FANFOU_API.'account/verify_credentials.json');  //  调用一个API功能以验证登录是否成功
  $GLOBALS['user']['fanuser'] = $user->id;
  _user_save_cookie(1);
  header('Location: '.BASE_URL.SF_CALLBACK.'?'.http_build_query(array('redir' => BASE_URL)));
  exit();
 } else {  //  用户选择登录饭否
  $response = fanfou_process(FANFOU_OAUTH.'request_token');  //  取得临时token
  parse_str($response, $token);
  $_SESSION['oauth_request_token_secret'] = $token['oauth_token_secret'];
  $authorise_url = FANFOU_OAUTH.'authorize?oauth_token='.$token['oauth_token']."&oauth_callback=".BASE_URL.FANFOU_CALLBACK;
  header("Location: $authorise_url");  //  转向饭否的authorize页面,请求用户授权
 }
}

取得access_token以后以及取得之前的请求,都需要按规范加入consumer_key/consumer_secret,并加以签名(签名内容包括规范要求的所有字段,不过这部分工作已经由oauth库处理了)。

function fan_oauth_sign(&$url, &$args) {
 require_once 'lib_oauth.php'; //  引用 lib_oauth 库
 $method = $args !== false ? 'POST' : 'GET';
 if (preg_match_all('#[?&]([^=]+)=([^&]+)#', $url, $matches, PREG_SET_ORDER)) {
  foreach ($matches as $match) {
   $args[$match[1]] = $match[2];
  }
  $url = substr($url, 0, strpos($url, '?'));
 }
 if (($oauth_token = $_GET['oauth_token']) && $_SESSION['oauth_request_token_secret']) {
  $oauth_token_secret = $_SESSION['oauth_request_token_secret'];
 } else {
  list($oauth_token, $oauth_token_secret) = explode('|', $GLOBALS['user']['fanpass']);
 }
  $keys = array(
  'oauth_key'  => OAUTH_CONSUMER_KEY,
 'oauth_secret' => OAUTH_CONSUMER_SECRET,
 'user_key' => $oauth_token,
 'user_secret' => $oauth_token_secret,
 );
 $url = oauth_sign_get($keys, $url, $args, $method);  // OAuth1.0签名

 if ($method == 'POST'){
 list($url, $args) = explode('?', $url, 2);
 }else{
 $args = false;
 }
}

function fanfou_process($url, $post_data = false) {
 fan_oauth_sign($url, $post_data);
 $result = api_process($url, $post_data);
 $response = $result['response'];
 $response_info = $result['response_info'];
    //  错误处理及json解码之类,从略……
}

这部分代码看起来比OAuth2.0并没有复杂太多,但这是在使用了oauth库的情况下才有这样的简化。用OAuth2.0的话则不需要依赖别的库就可以实现,而且还要简单。

整个客户端代码下载在Google Code


你可能感兴趣的:(OAuth)