OAuth2.0和OAuth1.0a協(xié)議實(shí)例化描述

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ù)器交互唱逢。

POST https://www.example.com/oauth/request_token

2、請(qǐng)求用戶授權(quán)Request Token屋休,客戶端使用webview打開(kāi)登錄頁(yè)面坞古,用戶登錄授權(quán)。

https://www.example.com/oauth/authorize

3劫樟、使用授權(quán)后的Request Token換取Access Token痪枫,與服務(wù)器交互。

POST https://www.example.com/oauth/access_token

獲取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保證安全澜汤。

https://gist.github.com/JakeWharton/f26f19732f0c5907e1ab

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舵匾,隨后出現(xiàn)的幾起案子俊抵,更是在濱河造成了極大的恐慌,老刑警劉巖坐梯,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徽诲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡烛缔,警方通過(guò)查閱死者的電腦和手機(jī)馏段,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)轩拨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)践瓷,“玉大人,你說(shuō)我怎么就攤上這事亡蓉≡未洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵砍濒,是天一觀的道長(zhǎng)淋肾。 經(jīng)常有香客問(wèn)我,道長(zhǎng)爸邢,這世上最難降的妖魔是什么樊卓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮杠河,結(jié)果婚禮上碌尔,老公的妹妹穿的比我還像新娘浇辜。我一直安慰自己,他們只是感情好唾戚,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布柳洋。 她就那樣靜靜地躺著,像睡著了一般叹坦。 火紅的嫁衣襯著肌膚如雪熊镣。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天募书,我揣著相機(jī)與錄音绪囱,去河邊找鬼。 笑死锐膜,一個(gè)胖子當(dāng)著我的面吹牛毕箍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播道盏,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼而柑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了荷逞?” 一聲冷哼從身側(cè)響起媒咳,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎种远,沒(méi)想到半個(gè)月后涩澡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坠敷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年妙同,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膝迎。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粥帚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出限次,到底是詐尸還是另有隱情芒涡,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布卖漫,位于F島的核電站费尽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏羊始。R本人自食惡果不足惜旱幼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望突委。 院中可真熱鬧柏卤,春花似錦叹誉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至忙灼,卻和暖如春匠襟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背该园。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工酸舍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人里初。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓啃勉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親双妨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淮阐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)刁品,斷路器泣特,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • OAuth 2.0 是目前比較流行的做法,它率先被Google, Yahoo, Microsoft, Facebo...
    半夜菊花茶閱讀 14,603評(píng)論 0 12
  • 1. 引言 如果你開(kāi)車(chē)去酒店赴宴挑随,你經(jīng)常會(huì)苦于找不到停車(chē)位而耽誤很多時(shí)間状您。是否有好辦法可以避免這個(gè)問(wèn)題呢?有的兜挨,聽(tīng)...
    youseewhat閱讀 1,500評(píng)論 1 5
  • 本文以一種簡(jiǎn)化的格式描述OAuth 2.0 膏孟,以幫助開(kāi)發(fā)人員和服務(wù)提供者實(shí)現(xiàn)該協(xié)議。 The OAuth 2 sp...
    KennethChen93閱讀 4,142評(píng)論 1 11
  • 今年年初拌汇,第一份實(shí)習(xí)柒桑,接觸了如何使用Facebook API, Twitter API...去獲取數(shù)據(jù),自動(dòng)發(fā)個(gè)F...
    Jason_Yuan閱讀 16,076評(píng)論 3 51