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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容