OAuth2.0和OAuth1.0a協議實例化描述

OAuth2.0協議

定義

OAuth: OAuth(開放授權)是一個開放標準,允許用戶授權第三方移動應用訪問他們存儲在另外的服務提供者上的信息,而不需要將用戶名和密碼提供給第三方移動應用或分享他們數據的所有內容。

開放授權(Open Authorization)

酒店泊車

  • 主鑰匙:完全功能
  • 泊車鑰匙:功能受限(行駛距離有限、無法打開后備箱、無法使用車內其他設備)

應用授權

小明是新浪微博的用戶,想要通過第三方客戶端來瀏覽微博,那第三方客戶端如何獲取小明在新浪微博的用戶信息和時間線?

直接授予賬號密碼的弊端:
1、第三方客戶端可能會保存小明的賬號密碼;
2、第三方客戶端可以訪問小明在新浪微博上的所有數據;
3、要收回第三方客戶端的權限,只有修改密碼。

OAuth 2.0協議

協議的參與者

OAuth的參與者至少有以下四個:

  • RO(Resource Owner):資源所有者,對資源具有授權能力的人。其實來講就是用戶,如上文的小明。
  • RS(Resource Server):資源服務器,它存儲資源,并處理對資源的訪問請求。如上文的新浪微博資源服務器
  • Client:第三方應用,它獲取RO的授權后就可以訪問RO的資源。如上文的第三方客戶端
  • AS(Authorization Server):授權服務器,它認證RO的身份,為RO提供授權審批流程,并最終頒發授權令牌(Access Token)。AS和RS的功能可以由同一個服務器來提供。

簡單的來講就是用戶第三方客戶端服務器。這個服務器有兩個工作:授權提供資源

OAuth的思路

OAuth在Client和RS之間設置了一層授權層。Client不能直接登錄RS,只能利用令牌來登錄授權層,而且這個令牌有權限范圍和有效期。

時序圖


基本流程:
1、用戶打開第三方客戶端(后面簡稱客戶端),客戶端引導用戶去授權。
2、用戶同意授權給客戶端,也就是點擊了同意授權的按鈕,之后客戶端會拿到授權證據。這里用戶如何批準是關鍵,后面會講到。
3、客戶端服務器請求訪問令牌(Access Token),同時出示前面的步驟拿到的授權證據
4、服務器通過認證后,向客戶端返回訪問令牌
5、客戶端攜帶訪問令牌去訪問服務器上的資源。
6、服務器驗證令牌的有效期和真偽,驗證通過后才能提供服務。

有兩個關鍵的東西,一個是用戶同意授權的授權證據,一個是用授權證據進一步請求拿到的訪問令牌

客戶端的授權

上面講到用戶客戶端授權這一步是關鍵。客戶端必須得到用戶的授權才能獲得令牌。Auth2.0定義了四種授權方式:

  • 授權碼模式
  • 簡化模式
  • 密碼模式
  • 客戶端模式

授權碼模式

授權碼模式是功能最完整、流程最嚴密的授權模式。其特點是通過Client的后臺服務器與AS進行互動。

基本流程:
1、客戶端初始化協議的執行流程,通過HTTP 302來重定向用戶代理服務器。這里的用戶代理基本上就是指瀏覽器。客戶端申請認證的URI包含以下參數:

  • response_type:授權類型,此處的值固定為“code”(必選)
  • client_id:客戶端的ID(必選)
  • redirect_uri:重定向URI(可選)
  • scope:申請的權限范圍(可選)
  • state:客戶端的當前狀態,可指定任意值,認證RS會原封不動地返回這個值

2、服務器認證用戶身份證,并提供頁面供用戶決定是否批準或拒絕客戶端的此次請求。
3、若請求被批準,服務器使用步驟(1)中客戶端提供的redirect_url重定向用戶代理到指定頁面。redirect_uri必須包含authorization_code,也就是我們前面所說的比較重要的授權證據。以及步驟(1)中Client提供的state。若請求被拒絕,AS將通過redirect_uri返回相應的錯誤信息。

  • code:授權碼(必選)。該碼的有效期應該很短,通常設為10分鐘,客戶端只能使用該碼一次,否則會被授權服務器拒絕。該碼與客戶端ID和重定向URI是一一對應的關系。
  • state:如果客戶端的請求中包含這個參數,認證服務器的回應也必須一模一樣包含這個參數。

4、客戶端authorization_code去訪問服務器以交換所需的access_token,也就是前面所說的訪問令牌客戶端請求信息中應包含用于認證客戶端身份所需的認證數據,以及上一步請求authorization_code時所用的redirect_uri

  • grant_type:授權模式,此處的值固定位“authorization_code”(必選)
  • code:上一步獲取的授權碼(必選)
  • redirect_uri:重定向URI,必須與步驟(1)中的該參數值保持一致(必選)
  • client_id:客戶端ID(必選)

5、服務器收到authorization_code時需要驗證客戶端的身份,并驗證收到的redirect_uri與步驟(3)請求authorization_code時所用的redirect_uri相匹配。如果驗證通過,AS將返回access_token以及refresh_token

  • access_token:訪問令牌
  • token_type:令牌類型
  • token_type:表示過期時間
  • refresh_token:更新令牌,用來獲取下一次的訪問令牌
  • scope:權限范圍,如果與客戶端申請的范圍一致,此項可省略

更新令牌

如果Client的訪問令牌過期,則需要使用更新令牌申請一個新的訪問令牌。
Client發出更新令牌的HTTP請求,包含以下參數:

  • grant_type:授權模式,此處的值固定為“refreshtoken”
  • refresh_token:之前收到的更新令牌
  • scope:申請的授權范圍,不可以超出上一次申請的范圍,如果省略該參數,則表示與上一次一致

為什么要先獲取authorizationCode再獲取accessTokenn呢?看官方的解釋:

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協議實例化描述

通過instagram登錄

時序圖

在上面Client拿到授權碼后去申請令牌時將client_secret發送給AS來證明自己的身份,即證明自己是User批注授權的Client。

具體步驟
1、首先第三方客戶端要在instagram的開發者平臺注冊為client,得到一個client_id和一個client_secret:

2、當用戶請求通過instagram登錄時,把用戶重定向到instagram提供的驗證授權頁面:

3、當用戶驗證通過且允許授權時instagram會將用戶重定向到之前設置的redirect_uri,并在后面附上授權碼,也就是授權證據

4、我們得到授權碼之后再去請求access_token:

5、最后返回access_token:


OAuth1.0a

OAuth 1.0a的登錄流程共有三個步驟

1、獲取未授權的Request Token,與服務器交互。

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

2、請求用戶授權Request Token,客戶端使用webview打開登錄頁面,用戶登錄授權。

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

3、使用授權后的Request Token換取Access Token,與服務器交互。

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

獲取Request Token

請求參數參數

  • oauth_consumer_key是注冊你的應用后獲得。
  • oauth_signature_method="HMAC-SHA1",僅支持HMAC-SHA1。
  • oauth_timestamp是當前時間戳,以秒為單位。
  • oauth_nonce是隨機字符串,與oauth_timestamp唯一對應,用來標識唯一,防止一些攻擊。
  • oauth_callback在注冊你的應用時需要提供的回調URL,需要URLEncode。
  • oauth_signature簽名結果,如何簽名最后一節討論,需要URLEncode。
  • oauth_version協議版本號。

返回結果

  • oauth_token:未授權的token。
  • oauth_token_secret:參與第三步的簽名。
  • oauth_callback_confirmed:對oauth_callback的確認信號 (true/false)

請求用戶授權

GET

https://www.example.com/oauth/authorize?oauth_token=hh5s93j4hdidpola

服務器返回重定向結果

http://printer.example.com/ready?
oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884

  • oauth_token:與第一步返回的結果是一樣的。
  • oauth_verifier:授權驗證碼。

獲取Access Token

請求參數

  • 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

關于簽名oauth_signature

開發者注冊APP時除了會得到oauth_consumer_key之外,還會得到一個consumer_secret。為了得到簽名,需要用以下的參數拼成字符串:

  • consumer secret - "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98"
  • oauth_consumer_key - GDdmIQH6jhtmLUypg82g
  • oauth_nonce - QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk
  • oauth_signature_method - HMAC-SHA1
  • oauth_timestamp - 1272323042
  • oauth_version - 1.0

拼接結果(HTTP請求方式(get或者post)+網址+上述各值):

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對base string進行HMAC-SHA1簽名,最終得到簽名結果oauth_signature。客戶端將oauth_signature以及除了consumer_secret以外的其他參數一并發給認證服務器。因為服務器端本身也保有客戶端申請時的consumer_secret,它再對以上信息進行簽名一次,將簽名結果與客戶端發來的結果進行比較,如果結果一致,就可以確認客戶端的身份。這種方式避免了傳遞consumer_secret。

OAuth2.0直接傳遞client_secret,是因為它改用SSL(HTTPS)確保安全性,因此也省去了簽名。

OAuth1.0a實例化描述

signpost是一個可以借助來快速實現OAuth1.0a授權的庫。iOS可以參考這個:simple-oauth1

  • 1、首先到Twitter注冊一個APP,注冊完成后會得到ConsumerKey和ConsumerSecretKey,以及完成授權需要的Url





  • 2、實例化一個CommonsHttpOAuthConsumer,設置consumerKey和secretKey;實例化一個CommonsHttpOAuthProvider, 設置完成授權需要的地址

mCommonsHttpOAuthConsumer = new CommonsHttpOAuthConsumer(consumerKey, secretKey);
mCommonsHttpOAuthProvider = new CommonsHttpOAuthProvider(REQUEST_URL, ACCESS_URL, AUTH_URL);
  • 3、調用mCommonsHttpOAuthProvider.retrieveRequestToken會得到一個地址,就是上面說的重定向用戶到登錄授權頁面的地址https://api.twitter.com/oauth/authorize?oauth_token=7Q8bwAAAAAAA7vV-AAABZJ1ryQQ,oauth_token就是未授權的Request Token,在Webview中加載該頁面,用戶登錄并授權后會重定向到下面的地址
https://github.com/okhochan?oauth_token=yznN4gAAAAAA7vV-AAABZJ1xrRM
&oauth_verifier=P9Uvc8PlZ1rU7trFcpEEmhnwnhbfSKXI

這個地址中可以拿到oauth_token和oauth_verifier

  • 4、利用上面拿到的oauth_verifier調用mCommonsHttpOAuthProvider.retrieveAccessToken(mCommonsHttpOAuthConsumer, oauth_verifier);即可在mCommonsHttpOAuthConsumer中拿到token和secret。

完整Android實現源碼

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可以使整個流程簡單許多,下面分析signpost主要做了哪些工作,可以大致分為兩個部分

  • 1、獲取未授權的Request Token,即引導用戶登錄授權的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,再利用我們初始化時設定的AUTH_URL拼成一個Url,也就是讓用戶登錄授權的地址。

再看看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;
    }

總結:OAuth2.0比OAuth1.0a少了一步,只有兩步:獲取authorizationCode,再利用authorizationCode和clientSecret請求accssToken;而OAuth1.0a有三步,先請求requestToken,利用requestToken讓用戶授權得到授權后的requestToken,最后再用requestToken請求accssToken。
OAuth1.0a最后一步沒有直接帶上clientSecret,而是采用簽名的方式,而OAuth2.0是直接帶上,因為有Https保證安全。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • OAuth 2.0 是目前比較流行的做法,它率先被Google, Yahoo, Microsoft, Facebo...
    半夜菊花茶閱讀 14,673評論 0 12
  • 1. 引言 如果你開車去酒店赴宴,你經常會苦于找不到停車位而耽誤很多時間。是否有好辦法可以避免這個問題呢?有的,聽...
    youseewhat閱讀 1,518評論 1 5
  • 本文以一種簡化的格式描述OAuth 2.0 ,以幫助開發人員和服務提供者實現該協議。 The OAuth 2 sp...
    JacoChan閱讀 4,238評論 1 11
  • 今年年初,第一份實習,接觸了如何使用Facebook API, Twitter API...去獲取數據,自動發個F...
    Jason_Yuan閱讀 16,157評論 3 51