Android學習筆記——OAuth完全手冊_國內篇


 

本文主要是介紹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.opensns.qq.com/wiki/%E3%80%90QQ%E7%99%BB%E5%BD%95%E3%80%91%E6%96%87%E6%A1%A3%E8%B5%84%E6%BA%90

  騰訊: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 1OAuth 1.0a

  • OAuth1認證基本步驟:  
  1. 獲取未授權的Request Token(temporary credentials)
  2. 請求用戶授權Request Token
  3. 使用授權后的Request Token換取Access Token(token credentials)
  4. 使用 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不再贅述。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM