access token_token failed是什么意思?

本意是在注销账号前保留之前的一些数据。决定用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。需要注意是一定要填写下面的回调地址:
新浪的是可以设置 本地的,例如
豆瓣的需要公网可以访问的回调地址,不同api不一样。
,根据语言选择。这里选择 php。理由是 php的web环境可以一键安装。需要注意到是,新浪的 SDK需要 php 开启 curl。不然会报错。
文档结构如下:
config.php &开发者配置文件
index.php &登录主入口
callback.php 回调处理
saetv2.ex.class.php &SDK 主文件,提供认证和api调用
weibolist.php 认证成功之后的数据请求和展示
引用官方的 SDK ,根据demo使用就行。这里重写一下 SDK 的请求过程。
session_start();
include_once( 'config.php' );
include_once( 'saetv2.ex.class.php' );
$o = new SaeTOAuthV2( WB_AKEY , WB_SKEY );
//获取请求 request_token 的 url
$code_url = $o-&getAuthorizeURL( WB_CALLBACK_URL );
&!DOCTYPE html&
&meta http-equiv="Content-Type" content="text/ charset=utf-8" /&
&title&新浪微博登录&/title&
&!-- 授权按钮 --&
&p&&a href="&?php echo $code_url?&"&&img src="weibo_login.png" title="点击进入授权页面" alt="点击进入授权页面" border="0" /&&/a&&/p&
主要做的事情和I获取 请求 &&&request_token 的url。然后跳转到官方提供的授权页面:
授权之后会重定向到回调 callback.pho.
session_start();
include_once( 'config.php' );
include_once( 'saetv2.ex.class.php' );
$o = new SaeTOAuthV2( WB_AKEY , WB_SKEY );
if (isset($_REQUEST['code'])) {
$keys = array();
$keys['code'] = $_REQUEST['code'];
$keys['redirect_uri'] = WB_CALLBACK_URL;
$token = $o-&getAccessToken( 'code', $keys ) ;
} catch (OAuthException $e) {
if ($token) {
$_SESSION['token'] = $
setcookie( 'weibojs_'.$o-&client_id, http_build_query($token) );
授权完成,&a href="weibolist.php"&进入你的微博列表页面&/a&&br /&
授权失败。
这一步主要是根据授权之后返回的 code 进行调用,请求 acess_token。
前面两部的方法 ,都在
saetv2.ex.class.php里实现,代码如下:
class OAuthException extends Exception {
* 新浪微博 OAuth 认证类(OAuth2)
* 授权机制说明请大家参考微博开放平台文档:{@link /wiki/Oauth2}
* @package sae
* @author Elmer Zhang
* @version 1.0
class SaeTOAuthV2 {
public $client_
public $client_
public $access_
public $http_
public $host = "/2/";
public $timeout = 30;
public $connecttimeout = 30;
public $ssl_verifypeer = FALSE;
public $format = 'json';
public $decode_json = TRUE;
public $http_
public $useragent = 'Sae T OAuth2 v0.1';
public $debug = FALSE;
public static $boundary = '';
* Set API URLS
function accessTokenURL()
{ return '/oauth2/access_token'; }
function authorizeURL()
{ return '/oauth2/authorize'; }
* construct WeiboOAuth object
function __construct($client_id, $client_secret, $access_token = NULL) {
$this-&client_id = $client_
$this-&client_secret = $client_
$this-&access_token = $access_
* authorize接口
* 对应API:{@link /wiki/Oauth2/authorize Oauth2/authorize}
* @param string $url 授权后的回调地址,站外应用需与回调地址一致,站内应用需要填写canvas page的地址
* @param string $response_type 支持的值包括 code 和token 默认值为code
* @param string $state 用于保持请求和回调的状态。在回调时,会在Query Parameter中回传该参数
* @param string $display 授权页面类型 可选范围:
默认授权页面
支持html5的手机
弹窗授权页
wap1.2页面
wap2.0页面
js-sdk 专用 授权页面是弹窗,返回结果为js-sdk回掉函数
- apponweibo
站内应用专用,站内应用不传display参数,并且response_type为token时,默认使用改display.授权后不会返回access_token,只是输出js刷新站内应用父框架
* @return array
function getAuthorizeURL( $url, $response_type = 'code', $state = NULL, $display = NULL ) {
$params = array();
$params['client_id'] = $this-&client_
$params['redirect_uri'] = $
$params['response_type'] = $response_
$params['state'] = $
$params['display'] = $
return $this-&authorizeURL() . "?" . http_build_query($params);
* access_token接口
* 对应API:{@link /wiki/OAuth2/access_token OAuth2/access_token}
* @param string $type 请求的类型,可以为:code, password, token
* @param array $keys 其他参数:
- 当$type为code时: array('code'=&..., 'redirect_uri'=&...)
- 当$type为password时: array('username'=&..., 'password'=&...)
- 当$type为token时: array('refresh_token'=&...)
* @return array
function getAccessToken( $type = 'code', $keys ) {
$params = array();
$params['client_id'] = $this-&client_
$params['client_secret'] = $this-&client_
if ( $type === 'code' ) {
$params['grant_type'] = 'authorization_code';
$params['code'] = $keys['code'];
$params['redirect_uri'] = $keys['redirect_uri'];
throw new OAuthException("wrong auth type");
$response = $this-&oAuthRequest($this-&accessTokenURL(), 'POST', $params);
$token = json_decode($response, true);
if ( is_array($token) && !isset($token['error']) ) {
$this-&access_token = $token['access_token'];
throw new OAuthException("get access token failed." . $token['error']);
* Format and sign an OAuth / API request
* @return string
function oAuthRequest($url, $method, $parameters, $multi = false) {
if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) {
$url = "{$this-&host}{$url}.{$this-&format}";
switch ($method) {
case 'GET':
$url = $url . '?' . http_build_query($parameters);
return $this-&http($url, 'GET');
$headers = array();
if (!$multi && (is_array($parameters) || is_object($parameters)) ) {
$body = http_build_query($parameters);
$body = self::build_http_query_multi($parameters);
$headers[] = "Content-Type: multipart/form- boundary=" . self::$
return $this-&http($url, $method, $body, $headers);
* Make an HTTP request
* @return string API results
function http($url, $method, $postfields = NULL, $headers = array()) {
$this-&http_info = array();
$ci = curl_init();
/* Curl settings */
curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($ci, CURLOPT_USERAGENT, $this-&useragent);
curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this-&connecttimeout);
curl_setopt($ci, CURLOPT_TIMEOUT, $this-&timeout);
curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ci, CURLOPT_ENCODING, "");
curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this-&ssl_verifypeer);
curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader'));
curl_setopt($ci, CURLOPT_HEADER, FALSE);
switch ($method) {
case 'POST':
curl_setopt($ci, CURLOPT_POST, TRUE);
if (!empty($postfields)) {
curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);
$this-&postdata = $
case 'DELETE':
curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE');
if (!empty($postfields)) {
$url = "{$url}?{$postfields}";
if ( isset($this-&access_token) && $this-&access_token )
$headers[] = "Authorization: OAuth2 ".$this-&access_
$headers[] = "API-RemoteIP: " . $_SERVER['REMOTE_ADDR'];
curl_setopt($ci, CURLOPT_URL, $url );
curl_setopt($ci, CURLOPT_HTTPHEADER, $headers );
curl_setopt($ci, CURLINFO_HEADER_OUT, TRUE );
$response = curl_exec($ci);
$this-&http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
$this-&http_info = array_merge($this-&http_info, curl_getinfo($ci));
$this-&url = $
if ($this-&debug) {
echo "=====post data======\r\n";
var_dump($postfields);
echo '=====info====='."\r\n";
print_r( curl_getinfo($ci) );
echo '=====$response====='."\r\n";
print_r( $response );
curl_close ($ci);
* Get the header info to store.
* @return int
function getHeader($ch, $header) {
$i = strpos($header, ':');
if (!empty($i)) {
$key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
$value = trim(substr($header, $i + 2));
$this-&http_header[$key] = $
return strlen($header);
public static function build_http_query_multi($params) {
if (!$params) return '';
uksort($params, 'strcmp');
$pairs = array();
self::$boundary = $boundary = uniqid('------------------');
$MPboundary = '--'.$
$endMPboundary = $MPboundary. '--';
$multipartbody = '';
foreach ($params as $parameter =& $value) {
if( in_array($parameter, array('pic', 'image')) && $value{0} == '@' ) {
$url = ltrim( $value, '@' );
$content = file_get_contents( $url );
$array = explode( '?', basename( $url ) );
$filename = $array[0];
$multipartbody .= $MPboundary . "\r\n";
$multipartbody .= 'Content-Disposition: form- name="' . $parameter . '"; filename="' . $filename . '"'. "\r\n";
$multipartbody .= "Content-Type: image/unknown\r\n\r\n";
$multipartbody .= $content. "\r\n";
$multipartbody .= $MPboundary . "\r\n";
$multipartbody .= 'content-disposition: form- name="' . $parameter . "\"\r\n\r\n";
$multipartbody .= $value."\r\n";
$multipartbody .= $endMP
getAuthorizeURL 方法是用来获取请求 request_token的地址。
getAccessToken 方法是获取 access_token
oAuthRequest 方法是用来发送请求
getHeader 我没发现有地方调用,但是没有他有不行,暂时不知道为什么。
至此Oauth认证结束。
认证就是为了得倒 access_token。本来我是要爬数据。才为了登录。后来发现直接使用 新浪的 ,自动生成 access_token。爬虫就直接用。当然,早没有发现,才促使我去研究了 Oauth认证,额外的收获吧。总而言之,人总是在逼迫中才能进步。
没明白什么意思
记生活写成长读人生由获取微信access_token引出的Java多线程并发问题
&&&&& access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
1、为了保密appsecrect,第三方需要一个access_token获取和刷新的中控服务器。而其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则会造成access_token覆盖而影响业务;
2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器对外输出的依然是老access_token,此时公众平台后台会保证在刷新短时间内,新老access_token都可用,这保证了第三方业务的平滑过渡;
3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。
简单起见,使用一个随servlet容器一起启动的servlet来实现获取access_token的功能,具体为:因为该servlet随着web容器而启动,在该servlet的init方法中触发一个线程来获得access_token,该线程是一个无线循环的线程,每隔2个小时刷新一次access_token。相关代码如下:
1)servlet代码:
public class InitServlet extends HttpServlet
private static final long serialVersionUID = 1L;
public void init(ServletConfig config) throws ServletException
new Thread(new AccessTokenThread()).start();
&2)线程代码:
public class AccessTokenThread implements Runnable
public static AccessToken accessT
public void run()
while(true)
AccessToken token = AccessTokenUtil.freshAccessToken(); // 从服务器刷新access_token
if(token != null){
accessToken =
System.out.println(&get access_token failed------------------------------&);
}catch(IOException e){
e.printStackTrace();
if(null != accessToken){
Thread.sleep((accessToken.getExpire_in() - 200) * 1000); // 休眠7000秒
Thread.sleep(60 * 1000); // 如果access_token为null,60秒后再获取
}catch(InterruptedException e){
Thread.sleep(60 * 1000);
}catch(InterruptedException e1){
e1.printStackTrace();
& 3)AccessToken代码:
public class AccessToken
private String access_
private long expire_
// access_token有效时间,单位为妙
public String getAccess_token() {
return access_
public void setAccess_token(String access_token) {
this.access_token = access_
public long getExpire_in() {
return expire_
public void setExpire_in(long expire_in) {
this.expire_in = expire_
&4)servlet在web.xml中的配置
&servlet-name&initServlet&/servlet-name&
&servlet-class&com.sinaapp.wx.servlet.InitServlet&/servlet-class&
&load-on-startup&0&/load-on-startup&
&/servlet&
因为initServlet设置了load-on-startup=0,所以保证了在所有其它servlet之前启动。
其它servlet要使用access_token的只需要调用 AccessTokenThread.accessToken即可。
引出多线程并发问题:
1)上面的实现似乎没有什么问题,但是仔细一想,AccessTokenThread类中的accessToken,它存在并发访问的问题,它仅仅由AccessTokenThread每隔2小时更新一次,但是会有很多线程来读取它,它是一个典型的读多写少的场景,而且只有一个线程写。既然存在并发的读写,那么上面的代码肯定是存在问题的。
&&&& 一般想到的最简单的方法是使用synchronized来处理:
public class AccessTokenThread implements Runnable
private static AccessToken accessT
public void run()
while(true)
AccessToken token = AccessTokenUtil.freshAccessToken(); // 从微信服务器刷新access_token
if(token != null){
AccessTokenThread.setAccessToken(token);
System.out.println(&get access_token failed&);
}catch(IOException e){
e.printStackTrace();
if(null != accessToken){
Thread.sleep((accessToken.getExpire_in() - 200) * 1000); // 休眠7000秒
Thread.sleep(60 * 1000); // 如果access_token为null,60秒后再获取
}catch(InterruptedException e){
Thread.sleep(60 * 1000);
}catch(InterruptedException e1){
e1.printStackTrace();
public synchronized static AccessToken getAccessToken() {
return accessT
private synchronized static void setAccessToken(AccessToken accessToken) {
AccessTokenThread2.accessToken = accessT
&accessToken变成了private,setAccessToken也变成了private,增加了同步synchronized访问accessToken的方法。
那么到这里是不是就完美了呢?就没有问题了呢?仔细想想,还是有问题,问题出在AccessToken类的定义上,它提供了public的set方法,那么所有的线程都在使用AccessTokenThread.getAccessToken()获得了所有线程共享的accessToken之后,任何线程都可以修改它的属性!!!!而这肯定是不对的,不应该的。
2)解决方法一:
&&& 我们让AccessTokenThread.getAccessToken()方法返回一个accessToken对象的copy,副本,这样其它的线程就无法修改AccessTokenThread类中的accessToken了。如下修改AccessTokenThread.getAccessToken()方法即可:
public synchronized static AccessToken getAccessToken() {
AccessToken at = new AccessToken();
at.setAccess_token(accessToken.getAccess_token());
at.setExpire_in(accessToken.getExpire_in());
&也可以在AccessToken类中实现clone方法,原理都是一样的。当然setAccessToken也变成了private。
3)解决方法二:
&&& 既然我们不应该让AccessToken的对象被修改,那么我们为什么不将accessToken定义成一个&不可变对象&?相关修改如下:
public class AccessToken
private final String access_
private final long expire_
// access_token有效时间,单位为妙
public AccessToken(String access_token, long expire_in)
this.access_token = access_
this.expire_in = expire_
public String getAccess_token() {
return access_
public long getExpire_in() {
return expire_
&如上所示,AccessToken所有的属性都定义成了final类型了,只提供构造函数和get方法。这样的话,其他的线程在获得了AccessToken的对象之后,就无法修改了。改修改要求AccessTokenUtil.freshAccessToken()中返回的AccessToken的对象只能通过有参的构造函数来创建。同时AccessTokenThread的setAccessToken也要修改成private,getAccessToken无须返回一个副本了。
注意不可变对象必须满足下面的三个条件:
a) 对象创建之后其状态就不能修改;
b) 对象的所有域都是final类型;
c) 对象是正确创建的(即在对象的构造函数中,this引用没有发生逸出);
4)解决方法三:
&&& 还有没有其他更加好,更加完美,更加高效的方法呢?我们分析一下,在解决方法二中,AccessTokenUtil.freshAccessToken()返回的是一个不可变对象,然后调用private的AccessTokenThread.setAccessToken(AccessToken accessToken)方法来进行赋值。这个方法上的synchronized同步起到了什么作用呢?因为对象时不可变的,而且只有一个线程可以调用setAccessToken方法,那么这里的synchronized没有起到&互斥&的作用(因为只有一个线程修改),而仅仅是起到了保证&可见性&的作用,让修改对其它的线程可见,也就是让其他线程访问到的都是最新的accessToken对象。而保证&可见性&是可以使用volatile来进行的,所以这里的synchronized应该是没有必要的,我们使用volatile来替代它。相关修改代码如下:
public class AccessTokenThread implements Runnable
private static volatile AccessToken accessT
public void run()
while(true)
AccessToken token = AccessTokenUtil.freshAccessToken(); // 从微信服务器刷新access_token
if(token != null){
AccessTokenThread2.setAccessToken(token);
System.out.println(&get access_token failed&);
}catch(IOException e){
e.printStackTrace();
if(null != accessToken){
Thread.sleep((accessToken.getExpire_in() - 200) * 1000); // 休眠7000秒
Thread.sleep(60 * 1000); // 如果access_token为null,60秒后再获取
}catch(InterruptedException e){
Thread.sleep(60 * 1000);
}catch(InterruptedException e1){
e1.printStackTrace();
private static void setAccessToken(AccessToken accessToken) {
AccessTokenThread2.accessToken = accessT
public static AccessToken getAccessToken() {
return accessT
&也可以这样改:
public class AccessTokenThread implements Runnable
private static volatile AccessToken accessT
public void run()
while(true)
AccessToken token = AccessTokenUtil.freshAccessToken(); // 从微信服务器刷新access_token
if(token != null){
accessToken =
System.out.println(&get access_token failed&);
}catch(IOException e){
e.printStackTrace();
if(null != accessToken){
Thread.sleep((accessToken.getExpire_in() - 200) * 1000); // 休眠7000秒
Thread.sleep(60 * 1000); // 如果access_token为null,60秒
&还可以这样改:
public class AccessTokenThread implements Runnable
public static volatile AccessToken accessT
public void run()
while(true)
AccessToken token = AccessTokenUtil.freshAccessToken(); // 从微信服务器刷新access_token
if(token != null){
accessToken =
System.out.println(&get access_token failed&);
}catch(IOException e){
e.printStackTrace();
if(null != accessToken){
Thread.sleep((accessToken.getExpire_in() - 200) * 1000); // 休眠7000秒
Thread.sleep(60 * 1000); // 如果access_token为null,60秒后再获取
}catch(InterruptedException e){
Thread.sleep(60 * 1000);
}catch(InterruptedException e1){
e1.printStackTrace();
&AccessTokenThread对accessToken属性变成了public可,其他线程可以直接访问:&AccessTokenThread.accessT
其实这个问题的关键是:在多线程并发访问的环境中如何正确的发布一个共享对象。
您对本文章有什么意见或着疑问吗?请到您的关注和建议是我们前行的参考和动力&&
您的浏览器不支持嵌入式框架,或者当前配置为不显示嵌入式框架。609 afw block
609 AFW BLOCK主题 : anysdk打包出来的越狱渠道包提示getAccessToken failed,哪位大神知道这个什么原因
级别: 新手上路
可可豆: 10 CB
威望: 10 点
在线时间: 5(时)
发自: Web Page
来源于&&分类
anysdk打包出来的越狱渠道包提示getAccessToken failed,哪位大神知道这个什么原因&&&
我用的Unity 1.5.2的框架,然后用的PP助手的1.4.4版本插件,打出来的包登录时提示Login failed: getAccessToken failed,这个谁知道什么原因吗?
级别: 论坛版主
UID: 262363
可可豆: 2517 CB
威望: 2007 点
在线时间: 2246(时)
发自: Web Page
服务端下发数据有问题
关注本帖(如果有新回复会站内信通知您)
8*2-5 正确答案:11
发帖、回帖都会得到可观的积分奖励。
按"Ctrl+Enter"直接提交
关注CocoaChina
关注微信 每日推荐
扫一扫 浏览移动版

我要回帖

更多关于 access token是什么 的文章

 

随机推荐