OAuth2.0協(xié)議
定義
OAuth: OAuth(開(kāi)放授權(quán))是一個(gè)開(kāi)放標(biāo)準(zhǔn),允許用戶授權(quán)第三方移動(dòng)應(yīng)用訪問(wèn)他們存儲(chǔ)在另外的服務(wù)提供者上的信息胞皱,而不需要將用戶名和密碼提供給第三方移動(dòng)應(yīng)用或分享他們數(shù)據(jù)的所有內(nèi)容绵疲。
開(kāi)放授權(quán)(Open Authorization)
酒店泊車(chē)
- 主鑰匙:完全功能
- 泊車(chē)鑰匙:功能受限(行駛距離有限梭姓、無(wú)法打開(kāi)后備箱仪搔、無(wú)法使用車(chē)內(nèi)其他設(shè)備)
應(yīng)用授權(quán)
小明是新浪微博的用戶忧换,想要通過(guò)第三方客戶端來(lái)瀏覽微博恬惯,那第三方客戶端如何獲取小明在新浪微博的用戶信息和時(shí)間線?
直接授予賬號(hào)密碼的弊端:
1亚茬、第三方客戶端可能會(huì)保存小明的賬號(hào)密碼酪耳;
2、第三方客戶端可以訪問(wèn)小明在新浪微博上的所有數(shù)據(jù)刹缝;
3碗暗、要收回第三方客戶端的權(quán)限,只有修改密碼梢夯。
OAuth 2.0協(xié)議
協(xié)議的參與者
OAuth的參與者至少有以下四個(gè):
-
RO
(Resource Owner):資源所有者讹堤,對(duì)資源具有授權(quán)能力的人。其實(shí)來(lái)講就是用戶厨疙,如上文的小明洲守。 -
RS
(Resource Server):資源服務(wù)器,它存儲(chǔ)資源沾凄,并處理對(duì)資源的訪問(wèn)請(qǐng)求梗醇。如上文的新浪微博資源服務(wù)器。 -
Client
:第三方應(yīng)用撒蟀,它獲取RO的授權(quán)后就可以訪問(wèn)RO的資源叙谨。如上文的第三方客戶端。 -
AS
(Authorization Server):授權(quán)服務(wù)器保屯,它認(rèn)證RO的身份手负,為RO提供授權(quán)審批流程,并最終頒發(fā)授權(quán)令牌(Access Token)姑尺。AS和RS的功能可以由同一個(gè)服務(wù)器來(lái)提供竟终。
簡(jiǎn)單的來(lái)講就是用戶
、第三方客戶端
切蟋、服務(wù)器
统捶。這個(gè)服務(wù)器有兩個(gè)工作:授權(quán)和提供資源。
OAuth的思路
OAuth在Client和RS之間設(shè)置了一層授權(quán)層。Client不能直接登錄RS喘鸟,只能利用令牌來(lái)登錄授權(quán)層匆绣,而且這個(gè)令牌有權(quán)限范圍和有效期。
時(shí)序圖
基本流程:
1什黑、
用戶
打開(kāi)第三方客戶端
(后面簡(jiǎn)稱(chēng)客戶端)崎淳,客戶端
引導(dǎo)用戶
去授權(quán)。2愕把、
用戶
同意授權(quán)給客戶端
凯力,也就是點(diǎn)擊了同意授權(quán)的按鈕,之后客戶端
會(huì)拿到授權(quán)證據(jù)
礼华。這里用戶如何批準(zhǔn)是關(guān)鍵,后面會(huì)講到拗秘。3圣絮、
客戶端
向服務(wù)器
請(qǐng)求訪問(wèn)令牌(Access Token)
,同時(shí)出示前面的步驟拿到的授權(quán)證據(jù)
雕旨。4扮匠、
服務(wù)器
通過(guò)認(rèn)證后,向客戶端
返回訪問(wèn)令牌
凡涩。5棒搜、
客戶端
攜帶訪問(wèn)令牌
去訪問(wèn)服務(wù)器
上的資源。6活箕、
服務(wù)器
驗(yàn)證令牌的有效期和真?zhèn)瘟︳铮?yàn)證通過(guò)后才能提供服務(wù)。
有兩個(gè)關(guān)鍵的東西育韩,一個(gè)是用戶同意授權(quán)的授權(quán)證據(jù)
克蚂,一個(gè)是用授權(quán)證據(jù)進(jìn)一步請(qǐng)求拿到的訪問(wèn)令牌
。
客戶端的授權(quán)
上面講到用戶
給客戶端
授權(quán)這一步是關(guān)鍵筋讨。客戶端
必須得到用戶
的授權(quán)才能獲得令牌埃叭。Auth2.0定義了四種授權(quán)方式:
- 授權(quán)碼模式
- 簡(jiǎn)化模式
- 密碼模式
- 客戶端模式
授權(quán)碼模式
授權(quán)碼模式是功能最完整、流程最嚴(yán)密的授權(quán)模式悉罕。其特點(diǎn)是通過(guò)Client的后臺(tái)服務(wù)器與AS進(jìn)行互動(dòng)赤屋。
基本流程:
1、客戶端
初始化協(xié)議的執(zhí)行流程壁袄,通過(guò)HTTP 302來(lái)重定向用戶代理
到服務(wù)器
类早。這里的用戶代理基本上就是指瀏覽器。客戶端
申請(qǐng)認(rèn)證的URI包含以下參數(shù):
-
response_type
:授權(quán)類(lèi)型嗜逻,此處的值固定為“code”(必選) -
client_id
:客戶端的ID(必選) -
redirect_uri
:重定向URI(可選) -
scope
:申請(qǐng)的權(quán)限范圍(可選) -
state
:客戶端的當(dāng)前狀態(tài)莺奔,可指定任意值,認(rèn)證RS會(huì)原封不動(dòng)地返回這個(gè)值
2、服務(wù)器
認(rèn)證用戶
身份證令哟,并提供頁(yè)面供用戶
決定是否批準(zhǔn)或拒絕客戶端
的此次請(qǐng)求恼琼。
3、若請(qǐng)求被批準(zhǔn)屏富,服務(wù)器
使用步驟(1)中客戶端
提供的redirect_url
重定向用戶代理
到指定頁(yè)面晴竞。redirect_uri
必須包含authorization_code
,也就是我們前面所說(shuō)的比較重要的授權(quán)證據(jù)
狠半。以及步驟(1)中Client提供的state
噩死。若請(qǐng)求被拒絕,AS將通過(guò)redirect_uri返回相應(yīng)的錯(cuò)誤信息神年。
-
code
:授權(quán)碼(必選)已维。該碼的有效期應(yīng)該很短,通常設(shè)為10分鐘已日,客戶端只能使用該碼一次垛耳,否則會(huì)被授權(quán)服務(wù)器拒絕。該碼與客戶端ID和重定向URI是一一對(duì)應(yīng)的關(guān)系飘千。 -
state
:如果客戶端的請(qǐng)求中包含這個(gè)參數(shù)堂鲜,認(rèn)證服務(wù)器的回應(yīng)也必須一模一樣包含這個(gè)參數(shù)。
4护奈、客戶端
拿authorization_code
去訪問(wèn)服務(wù)器
以交換所需的access_token
缔莲,也就是前面所說(shuō)的訪問(wèn)令牌
。客戶端
請(qǐng)求信息中應(yīng)包含用于認(rèn)證客戶端身份所需的認(rèn)證數(shù)據(jù)霉旗,以及上一步請(qǐng)求authorization_code
時(shí)所用的redirect_uri
痴奏。
-
grant_type
:授權(quán)模式,此處的值固定位“authorization_code”(必選) -
code
:上一步獲取的授權(quán)碼(必選) -
redirect_uri
:重定向URI厌秒,必須與步驟(1)中的該參數(shù)值保持一致(必選) -
client_id
:客戶端ID(必選)
5抛虫、服務(wù)器
收到authorization_code
時(shí)需要驗(yàn)證客戶端
的身份,并驗(yàn)證收到的redirect_uri
與步驟(3)請(qǐng)求authorization_code
時(shí)所用的redirect_uri相匹配简僧。如果驗(yàn)證通過(guò)建椰,AS將返回access_token
以及refresh_token
。
-
access_token
:訪問(wèn)令牌 -
token_type
:令牌類(lèi)型 -
token_type
:表示過(guò)期時(shí)間 -
refresh_token
:更新令牌岛马,用來(lái)獲取下一次的訪問(wèn)令牌 -
scope
:權(quán)限范圍棉姐,如果與客戶端申請(qǐng)的范圍一致,此項(xiàng)可省略
更新令牌
如果Client的訪問(wèn)令牌過(guò)期啦逆,則需要使用更新令牌申請(qǐng)一個(gè)新的訪問(wèn)令牌伞矩。
Client發(fā)出更新令牌的HTTP請(qǐng)求,包含以下參數(shù):
-
grant_type
:授權(quán)模式夏志,此處的值固定為“refreshtoken” -
refresh_token
:之前收到的更新令牌 -
scope
:申請(qǐng)的授權(quán)范圍乃坤,不可以超出上一次申請(qǐng)的范圍,如果省略該參數(shù),則表示與上一次一致
為什么要先獲取authorizationCode再獲取accessTokenn呢?看官方的解釋?zhuān)?/p>
When the application makes the request for the access token, that request is authenticated with the client secret, which reduces the risk of an attacker intercepting the authorization code and using it themselves. This also means the access token is never visible to the user, so it is the most secure way to pass the token back to the application, reducing the risk of the token leaking to someone else.
Authorization Code Grant
主要還是防止accessToken泄漏給他人湿诊,用secret code和authorization code可以確保client的身份是安全的狱杰。
OAuth2.0協(xié)議實(shí)例化描述
通過(guò)instagram登錄
時(shí)序圖
在上面Client拿到授權(quán)碼后去申請(qǐng)令牌時(shí)將client_secret發(fā)送給AS來(lái)證明自己的身份,即證明自己是User批注授權(quán)的Client厅须。
具體步驟
1仿畸、首先第三方客戶端要在instagram的開(kāi)發(fā)者平臺(tái)注冊(cè)為client,得到一個(gè)client_id和一個(gè)client_secret:
2朗和、當(dāng)用戶請(qǐng)求通過(guò)instagram登錄時(shí)错沽,把用戶重定向到instagram提供的驗(yàn)證授權(quán)頁(yè)面:
3、當(dāng)用戶驗(yàn)證通過(guò)且允許授權(quán)時(shí)instagram會(huì)將用戶重定向到之前設(shè)置的redirect_uri眶拉,并在后面附上授權(quán)碼千埃,也就是授權(quán)證據(jù)
:
4、我們得到授權(quán)碼之后再去請(qǐng)求access_token:
5忆植、最后返回access_token:
OAuth1.0a
OAuth 1.0a的登錄流程共有三個(gè)步驟:
1放可、獲取未授權(quán)的Request Token
,與服務(wù)器交互唱逢。
2、請(qǐng)求用戶授權(quán)Request Token
屋休,客戶端使用webview打開(kāi)登錄頁(yè)面坞古,用戶登錄授權(quán)。
3劫樟、使用授權(quán)后的Request Token換取Access Token
痪枫,與服務(wù)器交互。
獲取Request Token
請(qǐng)求參數(shù)參數(shù)
-
oauth_consumer_key
是注冊(cè)你的應(yīng)用后獲得叠艳。 -
oauth_signature_method
="HMAC-SHA1"奶陈,僅支持HMAC-SHA1。 -
oauth_timestamp
是當(dāng)前時(shí)間戳附较,以秒為單位吃粒。 -
oauth_nonce
是隨機(jī)字符串,與oauth_timestamp唯一對(duì)應(yīng)拒课,用來(lái)標(biāo)識(shí)唯一徐勃,防止一些攻擊。 -
oauth_callback
在注冊(cè)你的應(yīng)用時(shí)需要提供的回調(diào)URL早像,需要URLEncode僻肖。 -
oauth_signature
簽名結(jié)果,如何簽名最后一節(jié)討論卢鹦,需要URLEncode臀脏。 -
oauth_version
協(xié)議版本號(hào)。
返回結(jié)果
-
oauth_token
:未授權(quán)的token。 -
oauth_token_secret
:參與第三步的簽名揉稚。 -
oauth_callback_confirmed
:對(duì)oauth_callback的確認(rèn)信號(hào) (true/false)
請(qǐng)求用戶授權(quán)
GET
https://www.example.com/oauth/authorize?oauth_token=hh5s93j4hdidpola
服務(wù)器返回重定向結(jié)果
http://printer.example.com/ready?
oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884
-
oauth_token
:與第一步返回的結(jié)果是一樣的秒啦。 -
oauth_verifier
:授權(quán)驗(yàn)證碼。
獲取Access Token
請(qǐng)求參數(shù)
-
oauth_consumer_key
="dpf43f3p2l4k3l03", -
oauth_signature_method
="HMAC-SHA1", -
oauth_timestamp
="137131201", -
oauth_nonce
="walatlh", -
oauth_signature
="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D" -
oauth_token
="hh5s93j4hdidpola", -
oauth_verifier
="hfdp7dh39dks9884",
返回
-
oauth_token
=nnch734d00sl2jdk -
oauth_token_secret
=pfkkdhi9sl3r4s00
關(guān)于簽名oauth_signature
開(kāi)發(fā)者注冊(cè)APP時(shí)除了會(huì)得到oauth_consumer_key
之外窃植,還會(huì)得到一個(gè)consumer_secret
帝蒿。為了得到簽名,需要用以下的參數(shù)拼成字符串:
-
consumer secret
- "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" -
oauth_consumer_key
- GDdmIQH6jhtmLUypg82g -
oauth_nonce
- QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk -
oauth_signature_method
- HMAC-SHA1 -
oauth_timestamp
- 1272323042 -
oauth_version
- 1.0
拼接結(jié)果(HTTP請(qǐng)求方式(get或者post)+網(wǎng)址+上述各值):
POST&https%3A%2F%2Fapi.t.sina.com.cn%2Foauth%2Frequest_token&oauth_consumer_key%3DGDdmIQH6jhtmLUypg82g%26oauth_nonce%3DQP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1272323042%26oauth_version%3D1.0
然后用consumer_key
作為key對(duì)base string進(jìn)行HMAC-SHA1
簽名巷怜,最終得到簽名結(jié)果oauth_signature
葛超。客戶端將oauth_signature
以及除了consumer_secret以外的其他參數(shù)一并發(fā)給認(rèn)證服務(wù)器延塑。因?yàn)榉?wù)器端本身也保有客戶端申請(qǐng)時(shí)的consumer_secret绣张,它再對(duì)以上信息進(jìn)行簽名一次,將簽名結(jié)果與客戶端發(fā)來(lái)的結(jié)果進(jìn)行比較关带,如果結(jié)果一致侥涵,就可以確認(rèn)客戶端的身份。這種方式避免了傳遞consumer_secret宋雏。
OAuth2.0直接傳遞client_secret芜飘,是因?yàn)樗挠肧SL(HTTPS)確保安全性,因此也省去了簽名磨总。
OAuth1.0a實(shí)例化描述
signpost是一個(gè)可以借助來(lái)快速實(shí)現(xiàn)OAuth1.0a授權(quán)的庫(kù)嗦明。iOS可以參考這個(gè):simple-oauth1
-
1、首先到Twitter注冊(cè)一個(gè)APP蚪燕,注冊(cè)完成后會(huì)得到ConsumerKey和ConsumerSecretKey娶牌,以及完成授權(quán)需要的Url
2、實(shí)例化一個(gè)CommonsHttpOAuthConsumer馆纳,設(shè)置consumerKey和secretKey诗良;實(shí)例化一個(gè)CommonsHttpOAuthProvider, 設(shè)置完成授權(quán)需要的地址
mCommonsHttpOAuthConsumer = new CommonsHttpOAuthConsumer(consumerKey, secretKey);
mCommonsHttpOAuthProvider = new CommonsHttpOAuthProvider(REQUEST_URL, ACCESS_URL, AUTH_URL);
- 3鲁驶、調(diào)用
mCommonsHttpOAuthProvider.retrieveRequestToken
會(huì)得到一個(gè)地址鉴裹,就是上面說(shuō)的重定向用戶到登錄授權(quán)頁(yè)面的地址https://api.twitter.com/oauth/authorize?oauth_token=7Q8bwAAAAAAA7vV-AAABZJ1ryQQ
,oauth_token就是未授權(quán)的Request Token钥弯,在Webview中加載該頁(yè)面壹罚,用戶登錄并授權(quán)后會(huì)重定向到下面的地址
https://github.com/okhochan?oauth_token=yznN4gAAAAAA7vV-AAABZJ1xrRM
&oauth_verifier=P9Uvc8PlZ1rU7trFcpEEmhnwnhbfSKXI
這個(gè)地址中可以拿到oauth_token和oauth_verifier
- 4、利用上面拿到的oauth_verifier調(diào)用
mCommonsHttpOAuthProvider.retrieveAccessToken(mCommonsHttpOAuthConsumer, oauth_verifier);
即可在mCommonsHttpOAuthConsumer中拿到token和secret寿羞。
完整Android實(shí)現(xiàn)源碼
package com.honbr.twitteroauth;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import com.honbr.twitteroauth.databinding.ActivityMainBinding;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthProvider;
public class MainActivity extends AppCompatActivity {
public static final String CONSUMER_KEY = "***";
public static final String SECRET_KEY = "***";
public static final String TUMBLR_REQUEST = "https://api.twitter.com/oauth/request_token";
public static final String TWEETER_ACCESS = "https://api.twitter.com/oauth/access_token";
public static final String TWEETER_AUTH = "https://api.twitter.com/oauth/authorize";
public static final String CALLBACK_URL = "";
public static final String EXTRA_TOKEN = "token";
public static final String EXTRA_TOKEN_SECRET = "token_secret";
private ActivityMainBinding mViewBinding;
private CommonsHttpOAuthConsumer mCommonsHttpOAuthConsumer;
private CommonsHttpOAuthProvider mCommonsHttpOAuthProvider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mViewBinding.getRoot());
mViewBinding.loadingIndicator.setVisibility(View.VISIBLE);
mViewBinding.webView.loadData("<html><body style=\"background: #36465D\"></body></html>", "text/html", "utf-8");
io.reactivex.Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
mCommonsHttpOAuthConsumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, SECRET_KEY);
//Generate a new oAuthProvider object
mCommonsHttpOAuthProvider
= new CommonsHttpOAuthProvider(REQUEST——URL, ACCESS_URL, AUTH_URL);
//Retrieve the URL to which the user must be sent in order to authorize the consumer
e.onNext(mCommonsHttpOAuthProvider.retrieveRequestToken(
mCommonsHttpOAuthConsumer, CALLBACK_URL));
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String authUrl) throws Exception {
Log.i("LoginActivity.class", "accept: " + authUrl);
loadAuthUrl(authUrl);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
if (throwable.getCause() != null) {
Toast.makeText(MainActivity.this, throwable.getCause().getMessage(),
Toast.LENGTH_SHORT).show();
}
finish();
}
});
}
@SuppressLint("SetJavaScriptEnabled")
private void loadAuthUrl(String authUrl) {
mViewBinding.webView.getSettings().setJavaScriptEnabled(true);
mViewBinding.webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
mViewBinding.loadingProgress.setProgress(newProgress);
if (newProgress == 100) {
mViewBinding.loadingProgress.setVisibility(View.INVISIBLE);
} else {
mViewBinding.loadingProgress.setVisibility(View.VISIBLE);
}
}
});
mViewBinding.webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
if (!url.toLowerCase().contains(CALLBACK_URL.toLowerCase())) {
mViewBinding.loadingIndicator.setVisibility(View.INVISIBLE);
}
}
@SuppressWarnings({"UnusedAssignment", "unused"})
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.i("LoginActivity.class", "shouldOverrideUrlLoading: " + url);
if (url.toLowerCase().contains("oauth_token") && url.toLowerCase().contains("oauth_verifier")) {
Uri uri = Uri.parse(url);
//instantiate String variables to store OAuth & Verifier tokens
String strOAuthToken = "";
String strOAuthVerifier = "";
//Iterate through Parameters retrieved on the URL
for (String strQuery : uri.getQueryParameterNames()) {
switch (strQuery) {
case "oauth_token":
//Save OAuth Token
//Note : This is not the login token we require to set on JumblrToken
strOAuthToken = uri.getQueryParameter(strQuery);
break;
case "oauth_verifier":
//Save OAuthVerifier
strOAuthVerifier = uri.getQueryParameter(strQuery);
break;
}
}
retrieveAccessToken(strOAuthVerifier);
} else {
view.loadUrl(url);
}
return true;
}
});
mViewBinding.webView.loadUrl(authUrl);
}
private void retrieveAccessToken(final String strOAuthVerifier) {
mViewBinding.loadingIndicator.setVisibility(View.VISIBLE);
Observable.create(new ObservableOnSubscribe<String[]>() {
@Override
public void subscribe(ObservableEmitter<String[]> e) throws Exception {
String[] loginResults = new String[2];
mCommonsHttpOAuthProvider.retrieveAccessToken(mCommonsHttpOAuthConsumer, strOAuthVerifier);
//Check if tokens were received. If Yes, save them to SharedPreferences for later use.
if (!TextUtils.isEmpty(mCommonsHttpOAuthConsumer.getToken())) {
//Set the consumer key token in the LoginResult object
loginResults[0] = mCommonsHttpOAuthConsumer.getToken();
}
if (!TextUtils.isEmpty(mCommonsHttpOAuthConsumer.getTokenSecret())) {
//Set the Secret consumer key token in the LoginResult object
loginResults[1] = mCommonsHttpOAuthConsumer.getTokenSecret();
}
e.onNext(loginResults);
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String[]>() {
@Override
public void accept(String[] strings) throws Exception {
Intent intent = new Intent();
intent.putExtra(EXTRA_TOKEN, strings[0]);
intent.putExtra(EXTRA_TOKEN_SECRET, strings[1]);
setResult(0, intent);
finish();
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
if (throwable.getCause() != null) {
Toast.makeText(MainActivity.this, throwable.getCause().getMessage(),
Toast.LENGTH_SHORT).show();
}
finish();
}
});
}
}
可以看到利用signpost可以使整個(gè)流程簡(jiǎn)單許多猖凛,下面分析signpost主要做了哪些工作,可以大致分為兩個(gè)部分
- 1绪穆、獲取未授權(quán)的Request Token辨泳,即引導(dǎo)用戶登錄授權(quán)的Url虱岂;
- 2、利用auth_verifier獲得最終的token和secret
首先是mCommonsHttpOAuthProvider.retrieveRequestToken( mCommonsHttpOAuthConsumer, CALLBACK_URL))
public synchronized String retrieveRequestToken(OAuthConsumer consumer, String callbackUrl, String... customOAuthParams) throws OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException {
consumer.setTokenWithSecret((String)null, (String)null);
HttpParameters params = new HttpParameters();
params.putAll(customOAuthParams, true);
params.put("oauth_callback", callbackUrl, true);
this.retrieveToken(consumer, this.requestTokenEndpointUrl, params);
String callbackConfirmed = this.responseParameters.getFirst("oauth_callback_confirmed");
this.responseParameters.remove("oauth_callback_confirmed");
this.isOAuth10a = Boolean.TRUE.toString().equals(callbackConfirmed);
return this.isOAuth10a?OAuth.addQueryParameters(this.authorizationWebsiteUrl, new String[]{"oauth_token", consumer.getToken()}):OAuth.addQueryParameters(this.authorizationWebsiteUrl, new String[]{"oauth_token", consumer.getToken(), "oauth_callback", callbackUrl});
}
可以看到this.retrieveToken(consumer, this.requestTokenEndpointUrl, params);
這里獲取了Request Token菠红,再利用我們初始化時(shí)設(shè)定的AUTH_URL拼成一個(gè)Url第岖,也就是讓用戶登錄授權(quán)的地址。
再看看retrieveToken
做了什么
protected void retrieveToken(OAuthConsumer consumer, String endpointUrl, HttpParameters customOAuthParams) throws OAuthMessageSignerException, OAuthCommunicationException, OAuthNotAuthorizedException, OAuthExpectationFailedException {
Map<String, String> defaultHeaders = this.getRequestHeaders();
if(consumer.getConsumerKey() != null && consumer.getConsumerSecret() != null) {
HttpRequest request = null;
HttpResponse response = null;
try {
request = this.createRequest(endpointUrl);
Iterator i$ = defaultHeaders.keySet().iterator();
while(i$.hasNext()) {
String header = (String)i$.next();
request.setHeader(header, (String)defaultHeaders.get(header));
}
if(customOAuthParams != null && !customOAuthParams.isEmpty()) {
consumer.setAdditionalParameters(customOAuthParams);
}
if(this.listener != null) {
this.listener.prepareRequest(request);
}
consumer.sign(request);
if(this.listener != null) {
this.listener.prepareSubmission(request);
}
response = this.sendRequest(request);
int statusCode = response.getStatusCode();
boolean requestHandled = false;
if(this.listener != null) {
requestHandled = this.listener.onResponseReceived(request, response);
}
if(requestHandled) {
return;
}
if(statusCode >= 300) {
this.handleUnexpectedResponse(statusCode, response);
}
HttpParameters responseParams = OAuth.decodeForm(response.getContent());
String token = responseParams.getFirst("oauth_token");
String secret = responseParams.getFirst("oauth_token_secret");
responseParams.remove("oauth_token");
responseParams.remove("oauth_token_secret");
this.setResponseParameters(responseParams);
if(token == null || secret == null) {
throw new OAuthExpectationFailedException("Request token or token secret not set in server reply. The service provider you use is probably buggy.");
}
consumer.setTokenWithSecret(token, secret);
} catch (OAuthNotAuthorizedException var22) {
throw var22;
} catch (OAuthExpectationFailedException var23) {
throw var23;
} catch (Exception var24) {
throw new OAuthCommunicationException(var24);
} finally {
try {
this.closeConnection(request, response);
} catch (Exception var21) {
throw new OAuthCommunicationException(var21);
}
}
} else {
throw new OAuthExpectationFailedException("Consumer key or secret not set");
}
}
public synchronized HttpRequest sign(HttpRequest request) throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException {
if(this.consumerKey == null) {
throw new OAuthExpectationFailedException("consumer key not set");
} else if(this.consumerSecret == null) {
throw new OAuthExpectationFailedException("consumer secret not set");
} else {
this.requestParameters = new HttpParameters();
try {
if(this.additionalParameters != null) {
this.requestParameters.putAll(this.additionalParameters, false);
}
this.collectHeaderParameters(request, this.requestParameters);
this.collectQueryParameters(request, this.requestParameters);
this.collectBodyParameters(request, this.requestParameters);
this.completeOAuthParameters(this.requestParameters);
this.requestParameters.remove("oauth_signature");
} catch (IOException var3) {
throw new OAuthCommunicationException(var3);
}
String signature = this.messageSigner.sign(request, this.requestParameters);
OAuth.debugOut("signature", signature);
this.signingStrategy.writeSignature(signature, request, this.requestParameters);
OAuth.debugOut("Request URL", request.getRequestUrl());
return request;
}
}
```java
public String sign(HttpRequest request, HttpParameters requestParams) throws OAuthMessageSignerException {
try {
String keyString = OAuth.percentEncode(this.getConsumerSecret()) + '&' + OAuth.percentEncode(this.getTokenSecret());
byte[] keyBytes = keyString.getBytes("UTF-8");
SecretKey key = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(key);
String sbs = (new SignatureBaseString(request, requestParams)).generate();
OAuth.debugOut("SBS", sbs);
byte[] text = sbs.getBytes("UTF-8");
return this.base64Encode(mac.doFinal(text)).trim();
} catch (GeneralSecurityException var9) {
throw new OAuthMessageSignerException(var9);
} catch (UnsupportedEncodingException var10) {
throw new OAuthMessageSignerException(var10);
}
}
public class SignatureBaseString {
private HttpRequest request;
private HttpParameters requestParameters;
public SignatureBaseString(HttpRequest request, HttpParameters requestParameters) {
this.request = request;
this.requestParameters = requestParameters;
}
public String generate() throws OAuthMessageSignerException {
try {
String normalizedUrl = this.normalizeRequestUrl();
String normalizedParams = this.normalizeRequestParameters();
return this.request.getMethod() + '&' + OAuth.percentEncode(normalizedUrl) + '&' + OAuth.percentEncode(normalizedParams);
} catch (Exception var3) {
throw new OAuthMessageSignerException(var3);
}
}
public String normalizeRequestUrl() throws URISyntaxException {
URI uri = new URI(this.request.getRequestUrl());
String scheme = uri.getScheme().toLowerCase();
String authority = uri.getAuthority().toLowerCase();
boolean dropPort = scheme.equals("http") && uri.getPort() == 80 || scheme.equals("https") && uri.getPort() == 443;
if(dropPort) {
int index = authority.lastIndexOf(":");
if(index >= 0) {
authority = authority.substring(0, index);
}
}
String path = uri.getRawPath();
if(path == null || path.length() <= 0) {
path = "/";
}
return scheme + "://" + authority + path;
}
public String normalizeRequestParameters() throws IOException {
if(this.requestParameters == null) {
return "";
} else {
StringBuilder sb = new StringBuilder();
Iterator<String> iter = this.requestParameters.keySet().iterator();
for(int i = 0; iter.hasNext(); ++i) {
String param = (String)iter.next();
if(!"oauth_signature".equals(param) && !"realm".equals(param)) {
if(i > 0) {
sb.append("&");
}
sb.append(this.requestParameters.getAsQueryString(param, false));
}
}
return sb.toString();
}
}
}
public String writeSignature(String signature, HttpRequest request, HttpParameters requestParameters) {
StringBuilder sb = new StringBuilder();
sb.append("OAuth ");
if(requestParameters.containsKey("realm")) {
sb.append(requestParameters.getAsHeaderElement("realm"));
sb.append(", ");
}
HttpParameters oauthParams = requestParameters.getOAuthParameters();
oauthParams.put("oauth_signature", signature, true);
Iterator iter = oauthParams.keySet().iterator();
String header;
while(iter.hasNext()) {
header = (String)iter.next();
sb.append(oauthParams.getAsHeaderElement(header));
if(iter.hasNext()) {
sb.append(", ");
}
}
header = sb.toString();
OAuth.debugOut("Auth Header", header);
request.setHeader("Authorization", header);
return header;
}
總結(jié):OAuth2.0比OAuth1.0a少了一步试溯,只有兩步:獲取authorizationCode蔑滓,再利用authorizationCode和clientSecret請(qǐng)求accssToken;而OAuth1.0a有三步遇绞,先請(qǐng)求requestToken键袱,利用requestToken讓用戶授權(quán)得到授權(quán)后的requestToken,最后再用requestToken請(qǐng)求accssToken摹闽。
OAuth1.0a最后一步?jīng)]有直接帶上clientSecret蹄咖,而是采用簽名的方式,而OAuth2.0是直接帶上付鹿,因?yàn)橛蠬ttps保證安全澜汤。