本文主要是介紹OAuth認證以及各大平台粗略比較,如有紕漏,望請諒解。
轉載請注明:http://www.cnblogs.com/lingyun1120/archive/2012/07/11/2585767.html
Preface:
- 開發目的及進展
利用工作上關於SNS網站的研究,將多個SNS平台集成起來,一鍵分享。利用閑暇時間做了一個demo,還有很多需要改進的地方,請大家多多指教。
目前基本進展是完成了包括新浪微博、騰訊微博、QQ空間、人人網、開心網、豆瓣網、搜狐微博、網易微博在內的8個國內主要網站的OAuth認證以及簡單api的使用。為此我總結成一篇博客,詳細分析一下OAuth認證過程的要點,以及幾大平台的比較。
以下是我做的demo的相關UI以及登錄各個平台進行認證界面(webview)。
- OAuth介紹
在分享過程中不可避免的會考慮到用戶賬戶安全性的問題,第三方程序不應該直接接觸用戶賬戶信息,但是沒有賬戶信息,又如何取得SNS平台的數據呢?OAuth很好的解決了這個問題,從第三方發起認證過程,在webview或者瀏覽器中完成認證過程,獲得access token來代替賬戶密碼,從而可以獲取平台數據。OAUTH協議為用戶資源的授權提供了一個安全的、開放而又簡易的標准。同時,任何第三方都可以使用OAUTH認證服務,任何服務提供商都可以實現自身的OAUTH認證服務,因而OAUTH是開放的。
- 國內各平台支持程度
SNS |
OAuth1.0a |
OAuth2.0 |
備注 |
新浪微博 |
不支持(曾經支持) |
支持 |
最近已經放棄1.0認證。但是1.0的開發文檔還是可以學習。 |
騰訊微博 |
支持 |
支持 |
兩者都支持,向2.0轉變 |
QQ空間 |
不支持 |
支持 |
節操擺一邊,文檔干凈清晰 |
人人 |
不支持 |
支持 |
人人文檔很糟糕,用過的都知道 |
開心 |
支持 |
支持 |
兩者都支持,向2.0轉變 |
豆瓣 |
支持 |
不支持 |
豆瓣在開發平台方面確實做得不好,看它的文檔就一目了然。 |
搜狐微博 |
支持 |
不支持 |
文檔一般,logo素材太少 |
網易微博 |
支持 |
支持 |
文檔一般,logo素材豐富 |
- 關於開發文檔
文檔地址:
新浪:http://open.weibo.com/wiki/%E9%A6%96%E9%A1%B5
空間:
騰訊:http://wiki.open.t.qq.com/index.php/%E9%A6%96%E9%A1%B5
人人:http://wiki.dev.renren.com/wiki/%E9%A6%96%E9%A1%B5
開心:http://open.kaixin001.com/document.php
豆瓣:http://www.douban.com/service/apidoc/
搜狐:http://open.t.sohu.com/en/%E9%A6%96%E9%A1%B5
網易:http://open.t.163.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5
我們在進行開發時最重要的還是看平台的開發文檔,而從開發文檔也可以看出這個公司或者開發團隊的專業程度。以下是我總結的各個平台的優缺之處,也給大家使用開發文檔時增加一些幫助。
首先我覺得一個開放平台開發文檔比較重要的幾個點:OAuth文檔、API文檔、SDK、視覺(標示)素材、返回錯誤碼說明這幾個方面,當然這是從我現有的開發經驗來選擇,你可以根據實際情況側重其他方面進行比較。
(BTW,8個平台中豆瓣的文檔是最簡陋的,而且許多接口也沒有開放,無SDK,不過整體思路還是清晰的,開發時也不會有太多困惑,因此下面不再提及。)
OAuth文檔:所有文檔中以開心和騰訊微博做的最好,騰訊微博是有清晰示意圖,本文也是引用他們的圖片,而開心在於每個細節都描述的很清楚,在開發時不會有任何困惑的地方。最差的是人人和搜狐的文檔,人人整體還是可以的,但是因為他們對於session key的處理讓人很困惑,也不講清楚,而且文檔中有很多地方做的不夠好,連請求參數都不列清楚,而搜狐在於他們的OAuth文檔居然是外網的鏈接(包括OAuth官網地址,若干博客地址),既然做了就做完整。為了能夠在搜狐認證成功,我最后在API列表中找到接口,在找到參數列表。其他平台的話,新浪稍好一點,其他半斤八兩吧。
API文檔:包含接口說明、訪問權限、請求地址、支持格式、請求方式(POST/GET)、請求參數說明,返回結果(有例子)、字段說明。做的最好的是開心,除了這些說明,還會給出注意事項、調用示例、請求參數細分(api參數、OAuth1.0參數、OAuth2.0參數)。其他平台大同小異,不再贅述。
SDK:其實如果你不想了解OAuth認證以及調用API的細節之處,你完全可以使用它們的SDK。但是也有很多局限性:首先作為Android開發,有些網站不提供AndroidSDK(當然可以使用java SDK代替);其次SDK中很多代碼你並不需要使用到(比如人人的支付功能),直接導入SDK包也會造成程序的臃腫;再者,如何我們需要修改SDK的一些功能,閱讀SDK代碼的代價也是很大的,每個平台的SDK整體結構也是天差地別。這些網站中,新浪、開心、騰訊微博的SDK比較好(后來和facebook的SDK相比較,大家都是各種借鑒啊)。而搜狐最讓我傷心,居然什么SDK都沒有……
視覺素材:搜狐提供的非常稀少,其他平台都有豐富的素材。
綜上:開心網應該是做的比較好,為此我的demo主要是借鑒了它的SDK,給位讀者可以去開心網自己下載SDK研究,下面是關於關於OAuth1.0和OAuth2.0的介紹,如果你已經了解,請直接無視吧。
Part 1:OAuth 1.0a
- OAuth1認證基本步驟:
- 獲取未授權的Request Token(temporary credentials)
- 請求用戶授權Request Token
- 使用授權后的Request Token換取Access Token(token credentials)
- 使用 Access Token 訪問或修改受保護資源
示意圖(來自騰訊微博開發文檔)
- 請求簽名
所有的OAuth請求使用同樣的算法來生成(signature base string)簽名字符基串和簽名。
base string是把http方法名,請求URL以及請求參數用&字符連起來后做URL Encode編碼。具體來講,base string由http方法名,之后是&,接着是過url編碼(url-encoded)之后的url和訪問路徑及&。接下來,把所有的請求參數包括POST方法體中的參數,經過排序(按參數名進行文本排序,如果參數名有重復則再安參數值進行重復項目排序),使用%3D替代=號,並且使用%26作為每個參數之間的分隔符,拼接成一個字符串。
示意圖(來自騰訊微博開發文檔)
1 private static String generateSignature(String baseString, 2 String consumerKeySecret, String tokenSecret) { 3
4 byte[] byteHMAC = null; 5 try { 6 Mac mac = Mac.getInstance("HmacSHA1"); 7 SecretKeySpec spec; 8 String oauthSignature = encode(consumerKeySecret) + "&"
9 + ((tokenSecret != null) ? encode(tokenSecret) : ""); 10 spec = new SecretKeySpec(oauthSignature.getBytes(), "HmacSHA1"); 11 mac.init(spec); 12 byteHMAC = mac.doFinal(baseString.getBytes()); 13 } catch (InvalidKeyException e) { 14 e.printStackTrace(); 15 } catch (NoSuchAlgorithmException ignore) { 16 // should never happen
17 } 18 return new BASE64Encoder().encode(byteHMAC); 19 }
- 獲取未授權的Request Token
接口地址:
支持格式:OAuth HTTP 標准認證返回格式
HTTP請求方式:GET/POST
是否需要登錄:否
請求參數:
參數名 | 必選 | 介紹 |
oauth_consumer_key | true | API Key(組件信息中的API Key值) |
oauth_signature_method | true | 簽名方法,暫只支持HMAC-SHA1 |
oauth_signature | true | 簽名值,密鑰為:API Secret& |
oauth_timestamp | true | 時間戳,其值是距1970 00:00:00 GMT的秒數,必須是大於0的整數 |
oauth_nonce | true | 單次值,隨機生成的32位字符串(每次請求必須不同) |
oauth_callback | true | 認證成功后瀏覽器會被重定向到這個url中 |
oauth_version | false | 版本號,如果填寫必須為1.0 |
scope | false | 以空格分隔的權限列表,若不傳遞此參數,代表請求默認的basic權限。 如需調用擴展權限,必需傳遞此參數, |
返回參數:
參數名 | 必選 | 意義 |
oauth_token | true | 未授權的Request Token |
oauth_token_secret | true | 對應的Request Token Secret |
oauth_callback_confirmed | true | 對oauth_callback的確認信號 (true/false) |
注:有一些平台不需要輸入scope參數,在開發時請參照開發文檔。
1 public boolean getRequestToken(Context context, String callbackUrl, 2 String[] permissions) throws IOException { 3 Bundle params = new Bundle(); 4 params.putString("oauth_callback", callbackUrl); 5 if (permissions != null && permissions.length > 0) { 6 String scope = TextUtils.join(" ", permissions); 7 params.putString("scope", scope); 8 } 9 params = Util.generateURLParams(OAUTH1_REQUEST_TOKEN_URL, GET_METHOD, 10 params, CONSUMER_KEY, CONSUMER_SECRET, null); 11 String response = Util.openUrl(context, OAUTH1_REQUEST_TOKEN_URL, 12 GET_METHOD, params, null); 13 if (response == null) { 14 return false; 15 } 16
17 Bundle bundle = Util.decodeUrl(response); 18 String token = (String) bundle.get(OUATH_TOKEN); 19 String tokenSecret = (String) bundle.get(OUATH_TOKEN_SECRET); 20 if (token == null || tokenSecret == null) { 21 return false; 22 } 23
24 setRequestToken(token); 25 setRequestTokenSecret(tokenSecret); 26
27 return true; 28 }
- 請求用戶授權Request Token
接口地址:
支持格式:OAuth HTTP 標准認證返回格式
HTTP請求方式:GET/POST
是否需要登錄:否
請求參數:
參數名 | 必選 | 意義 |
oauth_token | true | 上一步中獲得的未授權的Request Token |
wap/client_type | false | 設置用戶認證界面形式,PC還是mobile,參照各自文檔 |
返回參數:
參數名 | 必選 | 意義 |
oauth_token | true | 用戶授權之后的Token值,與未授權Token值相同 |
oauth_verifier | true | 驗證碼 |
- 使用授權后的Request Token換取Access Token
接口地址:
支持格式:OAuth HTTP 標准認證返回格式
HTTP請求方式:GET/POST
是否需要登錄:否
請求參數:
參數名 | 必選 | 意義 |
oauth_consumer_key | true | API Key |
oauth_token | true | 第一步中獲得的Request Token |
oauth_signature_method | true | 簽名方法,暫只支持HMAC-SHA1 |
oauth_signature | true | 簽名值,(密鑰為:API Secret&Request Token Secret) |
oauth_timestamp | true | 時間戳, 其值是距1970 00:00:00 GMT的秒數,必須是大於0的整數 |
oauth_nonce | true | 單次值,隨機生成的32位字符串,防止重放攻擊(每次請求必須不同) |
oauth_verifier | true | 上一步請求授權request token時返回的驗證碼 |
oauth_version | flase | 版本號,如果填寫必須為1.0 |
返回參數:
參數名 | 必選 | 意義 |
oauth_token | true | Access Token |
oauth_token_secret | true | Access Token Secre |
1 public boolean getAccessToken(Context context, String requestToken, 2 String requestTokenSecret, String verifier) throws IOException { 3 Bundle params = new Bundle(); 4 params.putString(OUATH_TOKEN, requestToken); 5 if (verifier != null) 6 params.putString(OUATH_TOKEN_VERIFIER, verifier); 7 params = Util.generateURLParams(OAUTH1_ACCESS_TOKEN_URL, GET_METHOD, 8 params, CONSUMER_KEY, CONSUMER_SECRET, requestTokenSecret); 9 String response = Util.openUrl(context, OAUTH1_ACCESS_TOKEN_URL, 10 GET_METHOD, params, null); 11 if (response == null) { 12 return false; 13 } 14
15 Bundle bundle = Util.decodeUrl(response); 16 String token = (String) bundle.get(OUATH_TOKEN); 17 String tokenSecret = (String) bundle.get(OUATH_TOKEN_SECRET); 18 if (token == null || tokenSecret == null) { 19 return false; 20 } 21
22 setAccessToken(token); 23 setAccessTokenSecret(tokenSecret); 24
25 return true; 26 }
Part 2:OAuth 2.0
OAuth2.0和OAuth1.0的區別還是在於簡化了認證過程,不需要從未授權的Request Token轉化到授權Request Token,而是利用app key通過用戶授權生成access token但是,與1.0的不同之處是access token有自身的有效期,且不同平台、不同級別的程序有着不同的有效期,在程序開發中一定記得判斷access token是否過期,對於過期之后的處理方法主要是利用access token和refresh token重新生成access token或者重新利用app key向服務器發送請求生成access token。由於這個問題,與OAuth1.0基本一致不一樣,各個平台OAuth2.0做了不一樣的選擇。
OAuth2.0服務支持以下獲取Access Token的方式:
a. Authorization Code:Web Server Flow,適用於所有有Server端配合的應用。
b. Implicit Grant:User-Agent Flow,適用於所有無Server端配合的應用。
因為demo是無服務器的程式,所以我們采用Implicit Grant:User-Agent Flow的獲取方式。
示意圖(來自騰訊微博開發文檔)
- 獲取Access Token
為了獲取Access Token,應用需要將用戶瀏覽器(或手機/桌面應用中的瀏覽器組件)到OAuth2.0授權服務的“http://xxxxxxxxx/authorize”地址上,並帶上以下參數:
參數名 | 必選 | 介紹 |
client_id | true | 申請組件時獲得的API Key |
response_type | true | 此值固定為“token” |
redirect_uri | true | 授權后要回調的URI,即接受code的URI。對於無Web Server的應用, 其值可以是“oob”。 |
scope | false | 以空格分隔的權限列表,若不傳遞此參數,代表請求默認的basic權限。 如需調用擴展權限,必需傳遞此參數 |
state | false | 用於保持請求和回調的狀態,授權服務器在回調時(重定向用戶 瀏覽器到“redirect_uri”時),會在Query Parameter中原樣回傳該參數 |
display | false | 登錄和授權頁面的展現樣式,默認為“page”。手機訪問時,此參數無效 |
client | false | 是否為手機訪問。手機訪問:client=1;不是手機,無需次參數 |
若用戶登錄並接受授權,授權服務將重定向用戶瀏覽器到“redirect_uri”,並在Fragment中追加如下參數:
參數名 | 介紹 |
access_token | 要獲取的Access Token |
expires_in | Access Token的有效期,以秒為單位 |
refresh_token | 用於刷新Access Token 的 Refresh Token 一些平台不返回這個參數,需要程序員進行判斷處理 |
scope | Access Token最終的訪問范圍,即用戶實際授予的權限列表 |
state | 如果請求獲取Access Token時帶有state參數,則將該參數原樣返回 |
- 人人網和QQ空間的后續操作
人人網獲得access token還需要獲得session key和session secret,然后再調用api接口的時候利用app key、session key以及sig(簽名認證,點擊此處查看詳細算法),最近人人說可以使用access token來替換app key和session key的組合,貌似不需要session key,但是sig參數又需要通過session key才能獲得,真是多此一舉。
1 public void getRenrenSessionKey(Context context, String accessToken) { 2 if (accessToken == null || accessToken.length() < 1) { 3 return; 4 } 5 Bundle params = new Bundle(); 6 params.putString("oauth_token", accessToken); 7 try { 8 String sk = Util.openUrl( 9 "http://graph.renren.com/renren_api/session_key", "POST", 10 params); 11 JSONObject obj = new JSONObject(sk); 12 String error = obj.optString("error", null); 13 if (error != null) { 14 throw new SNSAuthError(obj.toString(), null, null); 15 } 16 String sessionKey = obj.getJSONObject("renren_token").getString( 17 "session_key"); 18 String sessionSecret = obj.getJSONObject("renren_token").getString( 19 "session_secret"); 20
21 long uid = obj.getJSONObject("user").getLong("id"); 22 // 服務器返回的過期時間單位為秒,故乘以1000
23 long expires = obj.getJSONObject("renren_token").getLong( 24 "expires_in") * 1000; 25 long current = System.currentTimeMillis(); 26 long expireTime = current + expires; 27
28 setSeeionKey(sessionKey); 29 setSeeionSecret(sessionSecret); 30 setSeeionExpires(expireTime); 31
32 Log.i(Util.LOG_TAG, "---login success sessionKey:" + sessionKey 33 + " expires:" + expires + " sessionSecret:" + sessionSecret 34 + " uid:" + uid); 35 } catch (JSONException e) { 36 throw new RuntimeException(e.getMessage(), e); 37 } 38 }
QQ空間沒有人人網這么麻煩,只是在返回參數中多了一個openId參數,這個參數在調用api接口是需要傳入,除此之外沒什么特殊之處,openid估計是為了他們平台的統一性設計的,無大礙。
1 public void getQzoneOpenId(Context context, String accessToken) { 2 if (accessToken == null || accessToken.length() < 1) { 3 return; 4 } 5 Bundle params = new Bundle(); 6 params.putString("access_token", accessToken); 7 String open = Util.openUrl("https://graph.z.qq.com/moc2/me", "GET", params); 8 String[] param = open.split("&"); 9 String[] openid = param[1].split("="); 10
11 setOpendId(openid[1]); 12
13 }
Summary
OAuth認證其實很簡單,調用api也是根據各個平台進行些許的調整而已,在進行開發的過程更細心的一點應該沒什么問題,如果你有問題,歡迎大家來交流。關於代碼的問題,大家把平台上相關的SDK下載下來稍微研究一下就可以了,本人是利用開心網的SDK修改的,新浪和騰訊的都不錯,以上信息,僅作參考,如有錯誤之處,望指正!謝謝!
過段時間在完成《國外篇》,流程類似,主要還是介紹各自特點吧,OAuth不再贅述。