转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-)
http://blog.csdn.net/floodingfire/article/details/8142974
自动登录代码
Weibo weibo = Weibo.getInstance();
AccessToken accessToken = new AccessToken(accessToken, SINA_API_SECRET);
accessToken.setExpiresIn("99999");
weibo.setAccessToken(accessToken);
在使用新浪开放平台的OAuth2.0授权后自动登录时,一直在报同一个异常:
com.weibo.net.WeiboException: HTTP/1.1 403 Forbidden
这个问题困扰我近月,上网搜寻答案也不得解脱。最后还是在新浪SDK源码中发现蛛丝马迹终于解决了这个问题。
Step1:在LogCat下查看错误信息
11:00:03.129: W/System.err(557): com.weibo.net.WeiboException: HTTP/1.1 403 Forbidden (1. 未授权)
11:00:03.149: W/System.err(557): at com.weibo.net.Utility.openUrl(Utility.java:335) (2. 错误发生在这里)
11:00:03.149: W/System.err(557): at com.weibo.net.Utility.openUrl(Utility.java:286)
11:00:03.178: W/System.err(557): at com.weibo.net.Weibo.request(Weibo.java:149)
11:00:03.178: W/System.err(557): at com.weibo.net.AsyncWeiboRunner$1.run(AsyncWeiboRunner.java:51)
11:00:03.218: W/share(557): WeiboException: com.weibo.net.WeiboException: HTTP/1.1 403 Forbidden
Step2:分析原因
首先错误消息是403状态码,有点儿常识的应该知道404代表没有访问资源、找不到资源等,而403大致是没有访问权、禁止访问等。(详细的解释Google之)
其实在新浪的文档和SDK的源码中都有关于异常的解释,比如在新浪SDK下,有这么一条注释:
com/weibo/net/WeiboException.java
403 Forbidden: 没有权限访问对应的资源.
可是明明传入了accessToken,怎么会没有访问权限呢?
不要急躁,顺着LogCat的信息顺藤摸瓜:StackTrace的栈顶信息显示错误最后出现在Utility.java中的openUrl()方法中:throw new WeiboException(String.format(status.toString()), statusCode);
public static String openUrl(Context context, String url, String method,
WeiboParameters params, String file, Token token) throws WeiboException {
String result = "";
try {
HttpClient client = getNewHttpClient(context);
HttpUriRequest request = null;
ByteArrayOutputStream bos = null;
if (method.equals("GET")) {
url = url + "?" + encodeUrl(params);
HttpGet get = new HttpGet(url);
request = get;
} else if (method.equals("POST")) {
HttpPost post = new HttpPost(url);
byte[] data = null;
bos = new ByteArrayOutputStream(1024 * 50);
if (!TextUtils.isEmpty(file)) {
Utility.paramToUpload(bos, params);
post.setHeader("Content-Type", MULTIPART_FORM_DATA + "; boundary=" + BOUNDARY);
Bitmap bf = BitmapFactory.decodeFile(file);
Utility.imageContentToUpload(bos, bf);
} else {
post.setHeader("Content-Type", "application/x-www-form-urlencoded");
String postParam = encodeParameters(params);
data = postParam.getBytes("UTF-8");
bos.write(data);
}
data = bos.toByteArray();
bos.close();
// UrlEncodedFormEntity entity = getPostParamters(params);
ByteArrayEntity formEntity = new ByteArrayEntity(data);
post.setEntity(formEntity);
request = post;
} else if (method.equals("DELETE")) {
request = new HttpDelete(url);
}
setHeader(method, request, params, url, token);
HttpResponse response = client.execute(request);
StatusLine status = response.getStatusLine();
int statusCode = status.getStatusCode();
if (statusCode != 200) {
result = read(response);
throw new WeiboException(String.format(status.toString()), statusCode);
}
// parse content stream from response
result = read(response);
return result;
} catch (IOException e) {
throw new WeiboException(e);
}
}
可以看到请求完毕以后,判断statusCode不为200(请求正常)状态码。也就是错误出现在发送请求之前,继续往上看,这个方法用的是Android中常用的一种网络通信方法:
HttpResponse response = client.execute(request);
那么在HttpClient执行此次请求之前,又发生了什么事情呢?
setHeader(method, request, params, url, token);
看方法的名称似乎像是设置HTTP的头部信息,ok,再跟进代码看看(其实有经验的人看到这个方法的参数,就已经明白是怎么回事了):
// 设置http头,如果authParam不为空,则表示当前有token认证信息需要加入到头中
public static void setHeader(String httpMethod, HttpUriRequest request,
WeiboParameters authParam, String url, Token token) throws WeiboException {
if (!isBundleEmpty(mRequestHeader)) {
for (int loc = 0; loc < mRequestHeader.size(); loc++) {
String key = mRequestHeader.getKey(loc);
request.setHeader(key, mRequestHeader.getValue(key));
}
}
if (!isBundleEmpty(authParam) && mAuth != null) {
String authHeader = mAuth.getWeiboAuthHeader(httpMethod, url, authParam,
Weibo.getAppKey(), Weibo.getAppSecret(), token);
if (authHeader != null) {
request.setHeader("Authorization", authHeader);
}
}
request.setHeader("User-Agent", System.getProperties().getProperty("http.agent")
+ " WeiboAndroidSDK");
}
这个方法的注释已经说明了问题,再仔细查看代码,问题就出在mAuth变量中,显然这个变量不是从刚才的方法传递过来的,那么这个方法怎么又要依赖一个莫名其妙的mAuth变量呢?
private static HttpHeaderFactory mAuth;
而查看整个Utility类(如何查看类中变量被使用的位置不必多说吧?),mAuth出现的次数并不多,除了在setHeader方法中有惊鸿一瞥,它的真正老家再另一个方法中:
public static void setAuthorization(HttpHeaderFactory auth) {
mAuth = auth;
}
看到这里我想大家除了想骂娘,没有别的心思了。
国内的大平台如新浪、腾讯、人人一贯如此,强内聚、弱耦合似乎只是个说法而已,虽然说一个大项目要做好扩展,但是好歹也要给个提示嘛!!!
Step3:解决问题
好了,小小的抱怨一下,我们的任务还没有完成,大致思路明确,要在此方法前初始化mAuth参数。
可是,参数是HttpHeaderFactory 对象啊,跟代码过去一看,又傻了:
public abstract class HttpHeaderFactory {
public static final String CONST_HMAC_SHA1 = "HmacSHA1";
public static final String CONST_SIGNATURE_METHOD = "HMAC-SHA1";
public static final String CONST_OAUTH_VERSION = "1.0";
public HttpHeaderFactory() {
}
...
这,尼玛!竟然是抽象类?本来憋着一股气,这又要怒气冲天了。
HOLD住,冲动易怒可不是好程序员的特质!既然是用的抽象类,那么前面的错误也明了,写这个代码的人可能是为了扩展性,既然是这样,他可能写好了实现类。如果连实现类都没有,大家就尽情的愤怒,然后安静的自己实现一个吧。
方案一:自己实现
....我这么懒,是不会做这种傻事的。
方案二:查找实现类
怎么个查找法?把所有的类都看一遍,看看是不是 extends HttpHeaderFactory? 是这么想的童鞋,先去面壁5分钟。
...
ok,有经验的程序员应该知道,在Eclipse中查看继承关系是有快捷方式的,选中类以后按:Ctrl + T。
这一下又要犯愁了,一下子揪出了他一家子,可见这个程序员确实是为了扩展,但是这么多的子类,到底用哪一个好呢?
你可以一个个看看,但是我用的是OAuth2.0,直接用试试Oauth2AccessTokenHeader好了。看着注释里那个半吊子英语,我又笑了~~should not be...
/**
* Encapsulation a http accessToken headers. the order of weiboParameters will not be changed.
* Otherwise the signature should not be calculated right.
* @author ZhangJie ([email protected])
*/
public class Oauth2AccessTokenHeader extends HttpHeaderFactory {
@Override
public String getWeiboAuthHeader(String method, String url, WeiboParameters params,
String app_key, String app_secret, Token token) throws WeiboException {
if(token == null){
return null;
}
return "OAuth2 " + token.getToken();
}
@Override
public WeiboParameters generateSignatureList(WeiboParameters bundle) {
return null;
}
@Override
public String generateSignature(String data, Token token) throws WeiboException{
return "";
}
@Override
public void addAdditionalParams(WeiboParameters des, WeiboParameters src) {
// TODO Auto-generated method stub
}
}
ok,直接使用无参构造方法初始化,作为参数,在使用相应API之前初始化mAuth参数,只有mAuth参数不为null时,Utility.setHeader才会拼凑出正确的HTTP Header来,否则header为空,token信息没有携带过去,就导致未授权错误,返回码是403了。
Utility.setAuthorization(new Oauth2AccessTokenHeader());
附录
正确的自动登录方法为:
Weibo weibo = Weibo.getInstance();
Utility.setAuthorization(new Oauth2AccessTokenHeader());
AccessToken accessToken = new AccessToken(accessToken, SINA_API_SECRET);
accessToken.setExpiresIn("99999");
weibo.setAccessToken(accessToken);