转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-)
http://my.oschina.net/ryanhoo/blog/86891
自动登录代码
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);