首页 > Android > 人人网的OAuth认证Android下的实现(非SDK方式)

人人网的OAuth认证Android下的实现(非SDK方式)

2011年8月14日 发表评论 阅读评论

前段时间准备做一个可以一键发布微博(状态)到人人网、QQ空间及各大微博的应用,新浪微博和腾讯微博的网上资料比较多,OAuth的认证方式也差不多,很容易就搞定了。只是人人网的搞得很另类,费了很大的周折才算完成,不过让人郁闷的是发布人人网状态的API属于“高级API”,要通过审核才能使用,而悲剧是我的应用最终没有通过审核,而未通过的审核的原因也把我雷到外焦里嫩,不发牢骚了。因为没有通过审核,这个应用也只好作罢,因为主要是为了能够同步人人网的(同步各大微博的应用早就存在了,我就不跟风了),虽然应用没有最终发布,但人人网的这套认证方式还是摸清了,今天整理了一下贴出来,希望对后来者有用。

之前也做过一个跟人人网相关的应用——生日提醒,同步人人网的好友信息,但是是通过人人网的Android SDK方式来实现的,这样比较省事,但是一个弊端就是需要引入人人网SDK,开发出来的应用体积会变大,尤其是我要开发同步各个平台的应用,如果都引入各自的SDK,体积会大到令人发指,所以选择了采用直接调用API的方式。下面是具体步骤:

一、注册人人网应用,获取API Key和Secret Key

这个就不用多说了,申请开发应用后就会获得。

二、获取accessToken

在之前,调用人人网API时必须提供api_key和session_key,而OAuth2.0验证授权后,使用获得的accessToken就可以了,比较省事,因此毫不犹豫采用这种方式。

和别的开放平台OAuth认证的一样,应用需要跳转到官方的一个认证页面,用户输入正确的信息确认授权后再返回应用,然后才能调用具体的API,OAuth认证的优点就不说了啊。认证我最初采用的方式是传入认证的URL,然后调用系统浏览器,用户确认后获取浏览器返回的信息。这种方式在Android自带的浏览器下没有问题,但是在如UC等一些第三方浏览器上,认证后并不会从浏览器返回应用,所以这种方式是不行的。我的解决方法是通过在本应用嵌入一个WebView,认证操作都放在这个WebView中,这样我们就可以自由操作并获取我们想要的数据了。

人人网OAuth2.0授权页面url是“https://graph.renren.com/oauth/authorize”,并带上参数“client_id”、“redirect_uri”、“response_type”。下面对着几个参数进行说明:

  • client_id:就是在第一步中申请的API Key;
  • redirect_uri:授权成功后跳转到的页面url,由于客户端类应用,没有自己的Web Server,在使用OAuth2.0 User-Agent Flow进行授权时,可以将redirect_uri指定为人人网提供的授权成功页面“http://graph.renren.com/oauth/login_success.html”就可以了;
  • response_type:返回的类型,我们需要的是accessToken,所以设置成token;
  • scope:需要请求的权限,多个权限以空格分隔,这里我们需要更新状态的status_update和发布照片的photo_upload权限。 

     

    不多说,下面上代码,是一个简单的Activity,里面只有一个WebView控件,在WebView中进行所有操作,获取accessToken保存在中:

    /**
     * 人人网授权页面
     * @author iStar
     *
     */
    public class RenrenAuthActivity extends Activity {
    	private WebView webView;
    	private String accessToken = null;
    	private SharedData share;
    	String authUrl = "https://graph.renren.com/oauth/authorize?client_id="
    		+ GlobalData.RENREN_KEY+"&response_type=token"
    		+ "&redirect_uri=http://graph.renren.com/oauth/login_success.html&display=mobile"
    		+ "&scope=status_update photo_upload";
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_oauth);
    		share = new SharedData(this);
    		webView = (WebView) findViewById(R.id.web);
    
    		bindViews();
    	}
    
    	private void bindViews() {
    		WebSettings settings = webView.getSettings();
    		settings.setJavaScriptEnabled(true);
            settings.setSupportZoom(true);
            settings.setBuiltInZoomControls(true);
    		webView.loadUrl(authUrl);
    		webView.requestFocusFromTouch();
    
    		WebViewClient wvc = new WebViewClient() {
    			@Override
    			public void onPageFinished(WebView view, String url) {
    				super.onPageFinished(view, url);
    				//人人网用户名和密码验证通过后,刷新页面时即可返回accessToken
    				String reUrl = webView.getUrl();
    				Log.d(GlobalData.TAG, "reUrl:" + reUrl);
    				if (reUrl.indexOf("access_token") != -1) {
    					//截取url中的accessToken
    					int startPos = reUrl.indexOf("token=") + 6;
    					int endPos = reUrl.indexOf("&expires_in");
    					accessToken = reUrl.substring(startPos, endPos);
    					//保存获取到的accessToken
    					share.saveRenrenToken(accessToken);
    					Log.d(GlobalData.TAG, "accessToken:" + accessToken);
    					Toast.makeText(RenrenAuthActivity.this, "验证成功", Toast.LENGTH_SHORT).show();
    					finish();
    				}
    			}
    		};
            webView.setWebViewClient(wvc);
    	}
    
    }

    三、通过出示AccessToken调用API

    我要实现的功能是调用人人网的API更新用户状态,对应的API是status.set,参数表如下:

    2011-08-14_17-09-32

    上图有详细的说明这里要说的是sig这个参数,为了确保应用与人人API服务器之间的安全通信,防止Secret Key盗用,数据篡改等恶意攻击,人人API 服务器使用了签名机制(即sig参数)来认证应用。签名是由请求参数和应用的私钥Secret Key经过MD5加密后生成的字符串。应用在调用人人API之前,要计算出签名,并追加到请求参数中。具体生产步骤如下:

    假设某个人人API需要3个参数,分别是“k1”、“k2”、“k3”,它们的值分别是“v1”、“v2”、“v3”,调用人人API的应用的私钥Secret Key为“secret_key”,计算方法如下所示。

    注意:这些参数中不包含sig(签名)参数,因为sig参数的值此时没有计算出来。

    • 将请求参数格式化为“key=value”格式,即“k1=v1”、“k2=v2”、“k3=v3”;
    • 将上诉格式化好的参数键值对,以字典序升序排列后,拼接在一起,即“k1=v1k2=v2k3=v3”;
    • 在上拼接好的字符串末尾追加上应用的Secret Key;
    • 上述字符串的MD5值即为签名的值。

    有点小麻烦,下面是我写的发布状态的方法,供参考:

    /**
     * 人人网操作工具类
     * @author iStar
     *
     */
    public class RenrenHelper {
    	public static final String API_URL = "http://api.renren.com/restserver.do";
    
    	private  SharedData share ;
    
    	public RenrenHelper(Context context) {
    		share = new SharedData(context);
    	}
    
    	/**
    	 * 更新状态
    	 * @param status 状态内容
    	 * @return  是否发布成功
    	 */
    	public  boolean updateStatus(String status) {
    		boolean success = false;
    		String accessToken = share.getRenrenToken();    //之前获取的accessToken
    		Log.d(GlobalData.TAG, "accessToken:" + accessToken);
    		String requestMethod = "status.set";            //接口名称
    		String v = "1.0";                               //API的版本号,请设置成1.0
    		String url = API_URL;                  //请求人人网开放平台API服务器的地址
    
    		//生成签名
    		StringBuilder sb = new StringBuilder();
    		sb.append("access_token=").append(accessToken)
    			.append("format=").append("JSON")
    			.append("method=").append(requestMethod)
    			.append("status=").append(status)
    			.append("v=").append(v)
    			.append(GlobalData.RENREN_SECRET);
    		String signature = MD5.getMD5(sb.toString());
    		Log.d(GlobalData.TAG, "signature:" + signature);
    
    		PostMethod method = new PostMethod(url);
    
            //将以上准备好的参数添加到method对象中
    		method.addParameter("access_token", accessToken);
    		method.addParameter("method", requestMethod);
    		method.addParameter("v", v);
    		method.addParameter("status", status);
            method.addParameter("format", "JSON");  //返回结果的形式,支持XML或者JSON两种形式,默认为XML
            method.addParameter("sig", signature);
    
    		HttpClient client = new HttpClient();
    		try {
    			client.executeMethod(method);
    			String result = method.getResponseBodyAsString();        //返回请求的结果
    			Log.d(GlobalData.TAG, "result:" + result);
    			if (result != null) {
    				JSONObject json = new JSONObject(result);
    				String ret = (String) json.get("result");
    				if (ret != null) {
    					success = ret.equals("1");
    				}
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		} 
    
    		return success;
    	}
    }

    上面用到了一个生产MD5的方法,下面是我很久以前写的一个生成一个字符串MD5码的类:

    import java.security.MessageDigest;
    
    /**
     * MD5工具类,用于生产字符串的MD5码
     * @author iStar
     *
     */
    public class MD5 {
    
    	public static String getMD5(String s) {
    		try {
    			MessageDigest md5 = MessageDigest.getInstance("MD5");
    
    			byte[] byteArray = s.getBytes("ISO-8859-1");
    			byte[] md5Bytes = md5.digest(byteArray);
    
    			StringBuffer hexValue = new StringBuffer();
    
    			for (int i = 0; i < md5Bytes.length; i++) {
    				int val = ((int) md5Bytes[i]) & 0xff;
    				if (val < 16)
    					hexValue.append("0");
    				hexValue.append(Integer.toHexString(val));
    			}
    
    			return hexValue.toString();
    
    		} catch (Exception e) {
    			e.printStackTrace();
    			return null;
    		}
    	}
    }
  • anyShare分享到:
              
    分类: Android 标签: , ,
    1. Gu
      2011年8月26日11:05 | #1

      偶然闯入LZ博客。。发现蛮有价值。。果断收藏之

      [回复]

    2. qianqian870208
      2011年8月26日15:17 | #2

      麻烦问你一下,SharedData、GlobalData是什么方法?

      [回复]

    3. qianqian870208
      2011年8月26日17:42 | #3

      麻烦楼主把SharedData、GlobalData这两个方法贴出来吧,我刚接触,不会写

      [回复]

      admin CHINA 回复:

      SharedData只是封装了SharedPreferance类,用来暂存数据的;GlobalData类里面放的是一些自己定义的常量。

      [回复]

      qianqian870208 CHINA Google Chrome Windows 回复:

      还是请求楼主,如果方便的话,可以把这两个方法告诉我么,我实在是写不出来了,跪求。。。谢谢楼主了

      [回复]

    4. 凯光
      2011年9月30日16:57 | #4

      你好,我用此方式可以获取到accessToken,没有问题,
      我的accessToken类似: 135782%7c6.1a56e6b21……
      问题就在这里:上面的%7c ,在发人人网新鲜事的时候,提示accessToken无效,然而将”%7c”替换成:”|” 就可以发了。
      这种情况您有遇见过吗?是不是拿到accessToken需要编码转换一下还是怎么样呢?
      等待回复,谢谢!

      [回复]

    5. 凯光
      2011年9月30日17:12 | #5

      ok,解决了。
      URLDecoder.decode(accessToken);

      [回复]

    1. 本文目前尚无任何 trackbacks 和 pingbacks.