新浪OAuth2.0自动登录报异常: 403 Forbidden

声明: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自动登录报异常: 403 Forbidden
    这一下又要犯愁了,一下子揪出了他一家子,可见这个程序员确实是为了扩展,但是这么多的子类,到底用哪一个好呢?


    你可以一个个看看,但是我用的是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);

你可能感兴趣的:(自动登录,403,OAuth2.0,新浪API,认证异常)