人人网的OAuth认证Android下的实现(非SDK方式)
前段时间准备做一个可以一键发布微博(状态)到人人网、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”。下面对着几个参数进行说明:
不多说,下面上代码,是一个简单的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;
}
}
}
| anyShare分享到: | |
| |
发表在《
发表在《
发表在《
偶然闯入LZ博客。。发现蛮有价值。。果断收藏之
[回复]
麻烦问你一下,SharedData、GlobalData是什么方法?
[回复]
麻烦楼主把SharedData、GlobalData这两个方法贴出来吧,我刚接触,不会写
[回复]
admin
回复:
八月 26th, 2011 at 下午 6:07
SharedData只是封装了SharedPreferance类,用来暂存数据的;GlobalData类里面放的是一些自己定义的常量。
[回复]
qianqian870208
回复:
八月 27th, 2011 at 下午 3:08
还是请求楼主,如果方便的话,可以把这两个方法告诉我么,我实在是写不出来了,跪求。。。谢谢楼主了
[回复]
你好,我用此方式可以获取到accessToken,没有问题,
我的accessToken类似: 135782%7c6.1a56e6b21……
问题就在这里:上面的%7c ,在发人人网新鲜事的时候,提示accessToken无效,然而将”%7c”替换成:”|” 就可以发了。
这种情况您有遇见过吗?是不是拿到accessToken需要编码转换一下还是怎么样呢?
等待回复,谢谢!
[回复]
ok,解决了。
URLDecoder.decode(accessToken);
[回复]