新浪微博API Oauth2.0 认证

原文链接: http://rsj217.diandian.com/post/2013-04-17/40050093587 

本意是在注销账号前保留之前的一些数据。决定用python 爬取收藏。可是未登录无法爬取。想要登录有两种办法,伪造浏览器登录。第二就是注册新浪开发者账号,通过Oauth认证调用其API。

Oauth 的原理搞了一天才明白。很多网站都提供多语言的Oauth。而 1.0 和 2.0 的最大差别就是多了一个 callback 回调页面。关于这方面的说明很少,搞得我一头雾水折腾了好久。总算明白了。

Oauth的原理

现在多用 oauth2.0. 具体可以看其官方文档。用下面的图简单说明:


Consumer指应用程序,User是用户,Service是服务器。大致过程为,User使用程序Consumer。C 向 Service请求一个未授权的令牌(Request_token),这个时候 S 验证 C的合法性(通过开发者的 APP_KEY 和 APP_SECREST)。然后重定向到一个授权页面(通常由服务方提供)。此时用户进行授权,即输入用户名和密码,用户会自动向服务器发送 authorize request_token。得到授权的 request_token。再然后,授权通过后,跳转到一个 callback 页面。通过request_token用来向服务器请求,换取 acess_token。至此,认证结束。接下来要请求数据,只需要根据文档 把 acess_token 当参数传给服务器。

这样说比较抽象,举个简单的例子:

有一个人 U 想去银行 S 取钱。U 没事情去柜台,就委托朋友 C 去柜台。当C 去了银行S,给S说:我要帮 U 取钱。S 先验证 C 的身份,是合法公民。然后 S 打电话给 U。说:C 要帮你取钱,你确定么,确定的话就输入用户名和密码。 U 确定了。此时,S 就给了一个 钥匙给 C。说:我们不提供自动服务,给你钥匙,自己去库房取。然后 C 拿着钥匙,就去取钱了。取完之后给力 U。U 很感激。

通过上面的解释,应该可以知道整个过程中有三个URL请求地址,如下图:


这写地址由服务方提供,上面那个就是 qq 公共平台的url。新浪有新浪自己的。关于第一步,请求 request_token的时候,服务器也要认证开发者帐号,也就是银行认证 C 的合法身份。

每个 开发者都有app_key 和 app_secret.  app_key称为密钥, app_secret为密匙。开发者和服务都知道。然后 ,开发者通过  app_key 和 app_secret 通过 一种加密(sha1)得到一个字符串secretcode 公钥。再把 app_key 和 secretcode 发送给服务器。服务器接收之后,将发送到 app_key和服务器存储的 app_secret 通过同样的加密手段得到一个 secretcode2 和发送过来的secretcode进行对比,如果一样,则可以证明是通过。这一点好处是安全。不怕被抓包。

新浪 Oauth的运用

简单知道原理,就可以进行认证。

准备工作

需要向新浪申请开发者帐号,然后创建一个应用。之后会得到一个 app_key 和 app_secret。需要注意是一定要填写下面的回调地址:


新浪的是可以设置 本地的,例如 http://127.0.0.1/oauth/callback.php

豆瓣的需要公网可以访问的回调地址,不同api不一样。

布署

可以下载 官方提供的 SDK ,根据语言选择。这里选择 php。理由是 php的web环境可以一键安装。需要注意到是,新浪的 SDK需要 php 开启 curl。不然会报错。

文档结构如下:

config.php  开发者配置文件

index.php  登录主入口

callback.php 回调处理

saetv2.ex.class.php  SDK 主文件,提供认证和api调用

weibolist.php 认证成功之后的数据请求和展示

引用官方的 SDK ,根据demo使用就行。这里重写一下 SDK 的请求过程。

index.php

 1 <?php
 2 session_start();
 3                                                     
 4 include_once( 'config.php' );
 5 include_once( 'saetv2.ex.class.php' );
 6                                                     
 7 $o = new SaeTOAuthV2( WB_AKEY , WB_SKEY );
 8 //获取请求 request_token 的 url
 9 $code_url = $o->getAuthorizeURL( WB_CALLBACK_URL );
10                                                     
11 ?>
12 <!DOCTYPE html>
13 <html>
14 <head>
15 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
16 <title>新浪微博登录</title>
17 </head>
18                                                     
19 <body>
20     <!-- 授权按钮 -->
21     <p><a href="<?php echo $code_url?>"><img src="weibo_login.png" title="点击进入授权页面" alt="点击进入授权页面" border="0" /></a></p>
22 </body>
23 </html>
View Code

 

主要做的事情和I获取 请求    request_token 的url。然后跳转到官方提供的授权页面:


 

授权之后会重定向到回调 callback.pho.

 1 <?php
 2 session_start();
 3                                    
 4 include_once( 'config.php' );
 5 include_once( 'saetv2.ex.class.php' );
 6                                    
 7 $o = new SaeTOAuthV2( WB_AKEY , WB_SKEY );
 8                                    
 9 if (isset($_REQUEST['code'])) {
10     $keys = array();
11     $keys['code'] = $_REQUEST['code'];
12     $keys['redirect_uri'] = WB_CALLBACK_URL;
13     try {
14         $token = $o->getAccessToken( 'code', $keys ) ;
15     } catch (OAuthException $e) {
16     }
17 }
18                                    
19 if ($token) {
20     $_SESSION['token'] = $token;
21     setcookie( 'weibojs_'.$o->client_id, http_build_query($token) );
22 ?>
23 授权完成,<a href="weibolist.php">进入你的微博列表页面</a><br />
24 <?php
25 } else {
26 ?>
27 授权失败。
28 <?php
29 }
30 ?>
View Code

 

这一步主要是根据授权之后返回的 code 进行调用,请求 acess_token。

前面两部的方法 ,都在

saetv2.ex.class.php里实现,代码如下:

  1 <?php
  2 /**
  3  * @ignore
  4  */
  5 class OAuthException extends Exception {
  6     // pass
  7 }
  8                             
  9                             
 10 /**
 11  * 新浪微博 OAuth 认证类(OAuth2)
 12  *
 13  * 授权机制说明请大家参考微博开放平台文档:{@link http://open.weibo.com/wiki/Oauth2}
 14  *
 15  * @package sae
 16  * @author Elmer Zhang
 17  * @version 1.0
 18  */
 19 class SaeTOAuthV2 {
 20                             
 21     public $client_id;
 22     public $client_secret;
 23     public $access_token;
 24     public $http_code;
 25     public $url;
 26     public $host = "https://api.weibo.com/2/";
 27     public $timeout = 30;
 28     public $connecttimeout = 30;
 29     public $ssl_verifypeer = FALSE;
 30     public $format = 'json';
 31     public $decode_json = TRUE;
 32     public $http_info;
 33     public $useragent = 'Sae T OAuth2 v0.1';
 34     public $debug = FALSE;
 35     public static $boundary = '';
 36                             
 37     /**
 38      * Set API URLS
 39      */
 40     /**
 41      * @ignore
 42      */
 43     function accessTokenURL()  { return 'https://api.weibo.com/oauth2/access_token'; }
 44     /**
 45      * @ignore
 46      */
 47     function authorizeURL()    { return 'https://api.weibo.com/oauth2/authorize'; }
 48                             
 49     /**
 50      * construct WeiboOAuth object
 51      */
 52     function __construct($client_id, $client_secret, $access_token = NULL) {
 53         $this->client_id = $client_id;
 54         $this->client_secret = $client_secret;
 55         $this->access_token = $access_token;
 56     }
 57                             
 58     /**
 59      * authorize接口
 60      *
 61      * 对应API:{@link http://open.weibo.com/wiki/Oauth2/authorize Oauth2/authorize}
 62      *
 63      * @param string $url 授权后的回调地址,站外应用需与回调地址一致,站内应用需要填写canvas page的地址
 64      * @param string $response_type 支持的值包括 code 和token 默认值为code
 65      * @param string $state 用于保持请求和回调的状态。在回调时,会在Query Parameter中回传该参数
 66      * @param string $display 授权页面类型 可选范围:
 67      *  - default       默认授权页面     
 68      *  - mobile        支持html5的手机     
 69      *  - popup         弹窗授权页      
 70      *  - wap1.2        wap1.2页面       
 71      *  - wap2.0        wap2.0页面       
 72      *  - js            js-sdk 专用 授权页面是弹窗,返回结果为js-sdk回掉函数      
 73      *  - apponweibo    站内应用专用,站内应用不传display参数,并且response_type为token时,默认使用改display.授权后不会返回access_token,只是输出js刷新站内应用父框架
 74      * @return array
 75      */
 76     function getAuthorizeURL( $url, $response_type = 'code', $state = NULL, $display = NULL ) {
 77         $params = array();
 78         $params['client_id'] = $this->client_id;
 79         $params['redirect_uri'] = $url;
 80         $params['response_type'] = $response_type;
 81         $params['state'] = $state;
 82         $params['display'] = $display;
 83         return $this->authorizeURL() . "?" . http_build_query($params);
 84     }
 85                             
 86     /**
 87      * access_token接口
 88      *
 89      * 对应API:{@link http://open.weibo.com/wiki/OAuth2/access_token OAuth2/access_token}
 90      *
 91      * @param string $type 请求的类型,可以为:code, password, token
 92      * @param array $keys 其他参数:
 93      *  - 当$type为code时: array('code'=>..., 'redirect_uri'=>...)
 94      *  - 当$type为password时: array('username'=>..., 'password'=>...)
 95      *  - 当$type为token时: array('refresh_token'=>...)
 96      * @return array
 97      */
 98     function getAccessToken( $type = 'code', $keys ) {
 99         $params = array();
100         $params['client_id'] = $this->client_id;
101         $params['client_secret'] = $this->client_secret;
102         if ( $type === 'code' ) {
103             $params['grant_type'] = 'authorization_code';
104             $params['code'] = $keys['code'];
105             $params['redirect_uri'] = $keys['redirect_uri'];
106         } else {
107             throw new OAuthException("wrong auth type");
108         }
109                             
110         $response = $this->oAuthRequest($this->accessTokenURL(), 'POST', $params);
111         $token = json_decode($response, true);
112         if ( is_array($token) && !isset($token['error']) ) {
113             $this->access_token = $token['access_token'];
114         } else {
115             throw new OAuthException("get access token failed." . $token['error']);
116         }
117         return $token;
118     }
119                             
120     /**
121      * Format and sign an OAuth / API request
122      *
123      * @return string
124      * @ignore
125      */
126     function oAuthRequest($url, $method, $parameters, $multi = false) {
127                             
128         if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) {
129             $url = "{$this->host}{$url}.{$this->format}";
130         }
131                             
132         switch ($method) {
133             case 'GET':
134                 $url = $url . '?' . http_build_query($parameters);
135                 return $this->http($url, 'GET');
136             default:
137                 $headers = array();
138                 if (!$multi && (is_array($parameters) || is_object($parameters)) ) {
139                     $body = http_build_query($parameters);
140                 } else {
141                     $body = self::build_http_query_multi($parameters);
142                     $headers[] = "Content-Type: multipart/form-data; boundary=" . self::$boundary;
143                 }
144                 return $this->http($url, $method, $body, $headers);
145         }
146     }
147                             
148     /**
149      * Make an HTTP request
150      *
151      * @return string API results
152      * @ignore
153      */
154     function http($url, $method, $postfields = NULL, $headers = array()) {
155         $this->http_info = array();
156         $ci = curl_init();
157         /* Curl settings */
158         curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
159         curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent);
160         curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);
161         curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout);
162         curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
163         curl_setopt($ci, CURLOPT_ENCODING, "");
164         curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);
165         curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader'));
166         curl_setopt($ci, CURLOPT_HEADER, FALSE);
167                             
168         switch ($method) {
169             case 'POST':
170                 curl_setopt($ci, CURLOPT_POST, TRUE);
171                 if (!empty($postfields)) {
172                     curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);
173                     $this->postdata = $postfields;
174                 }
175                 break;
176             case 'DELETE':
177                 curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE');
178                 if (!empty($postfields)) {
179                     $url = "{$url}?{$postfields}";
180                 }
181         }
182                             
183         if ( isset($this->access_token) && $this->access_token )
184             $headers[] = "Authorization: OAuth2 ".$this->access_token;
185                             
186         $headers[] = "API-RemoteIP: " . $_SERVER['REMOTE_ADDR'];
187         curl_setopt($ci, CURLOPT_URL, $url );
188         curl_setopt($ci, CURLOPT_HTTPHEADER, $headers );
189         curl_setopt($ci, CURLINFO_HEADER_OUT, TRUE );
190                             
191         $response = curl_exec($ci);
192         $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
193         $this->http_info = array_merge($this->http_info, curl_getinfo($ci));
194         $this->url = $url;
195                             
196         if ($this->debug) {
197             echo "=====post data======\r\n";
198             var_dump($postfields);
199                             
200             echo '=====info====='."\r\n";
201             print_r( curl_getinfo($ci) );
202                             
203             echo '=====$response====='."\r\n";
204             print_r( $response );
205         }
206         curl_close ($ci);
207         return $response;
208     }
209                             
210     /**
211      * Get the header info to store.
212      *
213      * @return int
214      * @ignore
215      */
216     function getHeader($ch, $header) {
217         $i = strpos($header, ':');
218         if (!empty($i)) {
219             $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
220             $value = trim(substr($header, $i + 2));
221             $this->http_header[$key] = $value;
222         }
223         return strlen($header);
224     }
225                             
226     /**
227      * @ignore
228      */
229     public static function build_http_query_multi($params) {
230         if (!$params) return '';
231                             
232         uksort($params, 'strcmp');
233                             
234         $pairs = array();
235                             
236         self::$boundary = $boundary = uniqid('------------------');
237         $MPboundary = '--'.$boundary;
238         $endMPboundary = $MPboundary. '--';
239         $multipartbody = '';
240                             
241         foreach ($params as $parameter => $value) {
242                             
243             if( in_array($parameter, array('pic', 'image')) && $value{0} == '@' ) {
244                 $url = ltrim( $value, '@' );
245                 $content = file_get_contents( $url );
246                 $array = explode( '?', basename( $url ) );
247                 $filename = $array[0];
248                             
249                 $multipartbody .= $MPboundary . "\r\n";
250                 $multipartbody .= 'Content-Disposition: form-data; name="' . $parameter . '"; filename="' . $filename . '"'. "\r\n";
251                 $multipartbody .= "Content-Type: image/unknown\r\n\r\n";
252                 $multipartbody .= $content. "\r\n";
253             } else {
254                 $multipartbody .= $MPboundary . "\r\n";
255                 $multipartbody .= 'content-disposition: form-data; name="' . $parameter . "\"\r\n\r\n";
256                 $multipartbody .= $value."\r\n";
257             }
258                             
259         }
260                             
261         $multipartbody .= $endMPboundary;
262         return $multipartbody;
263     }
264 }
265 ?>
View Code

getAuthorizeURL 方法是用来获取请求 request_token的地址。

getAccessToken 方法是获取 access_token

oAuthRequest 方法是用来发送请求

getHeader 我没发现有地方调用,但是没有他有不行,暂时不知道为什么。

至此Oauth认证结束。

认证就是为了得倒 access_token。本来我是要爬数据。才为了登录。后来发现直接使用 新浪的 api 测试接口,自动生成access_token。爬虫就直接用。当然,早没有发现,才促使我去研究了 Oauth认证,额外的收获吧。总而言之,人总是在逼迫中才能进步。

posted @ 2014-10-14 14:45  进击的小矮人  阅读(898)  评论(0编辑  收藏  举报