前段时间准备做一个可以一键发布微博(状态)到人人网、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,参数表如下:
上图有详细的说明这里要说的是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;
- }
- }
- }