oauth第三方登陸scribejava庫封裝qq及微信api

ScribeJava 是一個簡單的 Java 實現的 OAuth/OAuth2 庫,若是要支持qq及微信第三方登陸須要本身寫封裝協議的代碼。然而介紹如何本身封裝api的文章很是少。所以打算寫一些基礎東西,第一次寫長博客,若有不足歡迎指正。我會在在文中分享關於scribejava中qq與微信的封裝類,懶人的話能夠略過封裝代碼的說明,複製到項目中去(根據scribejava-apis版本6.0封裝,其餘版本可能會由不兼容狀況)。java

1.流程簡介

各類第三方對接oauth協議的步驟基本相同,詳細介紹及流程可參考知乎 OAuth 2 詳解,寫得簡單易懂。git

文章中提到的OAuth 定義了四種角色github

  • 資源擁有者 (Resource Owner)
  • 客戶端 (Client)
  • 資源服務器 (Resource Server)
  • 受權服務器 (Authorization Server)

我在開發中按經常使用的Grant Type爲受權碼(Authorization Code)的方式做爲協議進行開發。另一種隱式(Grant Type: Implicit)方式不進行討論。json

再盜一下做者的受權碼的流程圖。 api

受權流程

上面提到的客戶端 (Client),能夠理解爲咱們所要來第三方登陸的項目後臺。Client經過處理與其餘3個角色的關係完成最終的受權。bash

在封裝scribejava的api時,面對來自不一樣第三方登陸提供者(qq/微信),咱們所要考慮的實際就是這5步流程中1,2,4步所要發送的url如何拼接處理的問題。服務器

2.scribeJava引入與說明

maven項目pom文件中引入最新版scribejava。微信

<!--第三方oauth2登陸-->
<dependency>
    <groupId>com.github.scribejava</groupId>
    <artifactId>scribejava-apis</artifactId>
    <version>6.0.0</version>
</dependency>
複製代碼

scribejava-apis包含一些做者對第三方oauth受權提供方的封裝,惋惜沒有對qq與微信進行封裝。若是想用純淨一點的scribejava,那pom文件能夠設置以下:app

<dependency>
    <groupId>com.github.scribejava</groupId>
    <artifactId>scribejava-core</artifactId>
    <version>6.0.0</version>
</dependency>
複製代碼

3.封裝的代碼

廢話很少說,直接先放我qq封裝的代碼maven

public class QQApi20 extends DefaultApi20{

    protected QQApi20(){
    }

    public static QQApi20 instance(){return QQApi20.InstanceHolder.INSTANCE;}


    @Override
    public String getAccessTokenEndpoint() {
        return "https://graph.qq.com/oauth2.0/token";
    }

    @Override
    protected String getAuthorizationBaseUrl() {
        return "https://graph.qq.com/oauth2.0/authorize";
    }

    @Override
    public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
        return OAuth2AccessTokenExtractor.instance();
    }

    /**
     * 添加appId跟appKey採用在http的請求body中添加
     * @return
     */
    @Override
    public ClientAuthentication getClientAuthentication() {
        return RequestBodyAuthenticationScheme.instance();
    }

    /**
     * 受權的token在http請求的body中傳遞
     * @return
     */
    @Override
    public BearerSignature getBearerSignature() {
        return BearerSignatureURIQueryParameter.instance();
    }


    private static class InstanceHolder {

        private static final QQApi20 INSTANCE = new QQApi20();
        private InstanceHolder() {
        }

    }
}
複製代碼

再就是wechat的封裝代碼:

public class WechatApi20 extends DefaultApi20 {

    protected WechatApi20(){}

    public static WechatApi20 instance(){return WechatApi20.InstanceHolder.INSTANCE;}

    @Override
    public String getAccessTokenEndpoint() {
        return "https://api.weixin.qq.com/sns/oauth2/access_token";
    }

    @Override
    public String getAuthorizationUrl(String responseType, String apiKey, String callback, String scope, String state,
                                      Map<String, String> additionalParams) {
        final ParameterList parameters = new ParameterList(additionalParams);
        parameters.add(OAuthConstants.RESPONSE_TYPE, "code");
        parameters.add("appid", apiKey);

        if (callback != null) {
            parameters.add(OAuthConstants.REDIRECT_URI, callback);
        }

        if (scope != null) {
            parameters.add(OAuthConstants.SCOPE, scope);
        }

        if (state != null) {
            parameters.add(OAuthConstants.STATE, state);
        }

        return parameters.appendTo("https://open.weixin.qq.com/connect/qrconnect");
    }

    @Override
    protected String getAuthorizationBaseUrl() {
        throw new UnsupportedOperationException("use getAuthorizationUrl instead");
    }

    @Override
    public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
        return OAuth2AccessTokenJsonExtractor.instance();
    }

    /**
     * 添加appId跟appKey採用在http的請求body中添加
     * @return
     */
    @Override
    public ClientAuthentication getClientAuthentication() {
        return RequestBodyAuthenticationScheme.instance();
    }

    /**
     * 受權的token在http請求的body中傳遞
     * @return
     */
    @Override
    public BearerSignature getBearerSignature() {
        return BearerSignatureURIQueryParameter.instance();
    }

    @Override
    public WechatOAuthService createService(String apiKey, String apiSecret, String callback, String scope,
                                            OutputStream debugStream, String state, String responseType, String userAgent,
                                            HttpClientConfig httpClientConfig, HttpClient httpClient){
        return new WechatOAuthService(this, apiKey, apiSecret, callback, scope, state, responseType, userAgent,
                httpClientConfig, httpClient);
    }

    private static class InstanceHolder {

        private static final WechatApi20 INSTANCE = new WechatApi20();
        private InstanceHolder() {
        }

    }
}

複製代碼

介紹一下我是如何進行封裝的。準備前先進入qq開放平臺,瞭解qq互聯接口的形式 (qq互聯連接)。

這裏本身封裝時若是繼承DefaultApi20類,咱們的封裝類會按oauth2經常使用的協議規則進行拼接操做。

  • 受權url封裝處理

咱們平時點擊qq登陸會跳轉到的qq受權登陸頁面

咱們構造跳轉這個url到第三方服務上須要路徑首先要重寫方法getAuthorizationBaseUrl;其次一個完整的受權url請求會帶一些參數,有可能要重寫getAuthorizationUrl方法。

qq完整連接相似下面:

https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=[YOUR_APPID]&redirect_uri=[YOUR_REDIRECT_URI]&scope=[THE_SCOPE]
複製代碼

微信的完整連接相似下面:

https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
複製代碼

參數上qq的client_id與微信appid實際上是同一個東西,只是人家用不一樣的單詞表示了😠。父類DefaultApi20類中的處理過程正好跟qq的oauth2協議基本相同,因此對於qq只重寫getAuthorizationBaseUrl這個方法便可。 而微信的oauth2協議跟DefaultApi20有一些不一樣,這就要重寫一下getAuthorizationUrl方法了。

重寫完這兩個方法後,還要重寫getClientAuthentication(),若是不寫,其實當咱們調用getAuthorizationUrl()會發現獲取的url缺乏參數client_id,是在header頭中發現傳入了client_id與client_secret的base64編碼信息。這樣不是咱們想要的接入方式。因此重寫getClientAuthentication(),讓clien_id在請求url的參數中帶出來。

@Override
    public ClientAuthentication getClientAuthentication() {
        return RequestBodyAuthenticationScheme.instance();
    }
複製代碼
  • 第三方應用請求服務端,獲取 access token

用戶經過受權頁面獲取到code值,這個值在後臺controller捕獲處理到後就要再請求第三方服務獲取accessToken,這一步父類DefaultApi20默認交給OAuth20Service處理了,獲取到的accessToken會由重寫的getAccessToken方法返回。而微信在這一步又在搞事,用默認的OAuth20Service沒法獲取到accessToken值,由於微信的appid跟secret是必填項,因此我又新建了一個WechatOAuthService類把出現的差異處理一下。

public class WechatOAuthService extends OAuth20Service{

    public WechatOAuthService(DefaultApi20 api, String apiKey, String apiSecret, String callback, String scope,
                              String state, String responseType, String userAgent, HttpClientConfig httpClientConfig,
                              HttpClient httpClient) {
        super(api, apiKey, apiSecret, callback, scope, state, responseType, userAgent, httpClientConfig, httpClient);
    }

    @Override
    protected OAuthRequest createAccessTokenRequest(String oauthVerifier) {
        final DefaultApi20 api = getApi();
        final OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
        request.addBodyParameter("appid", getApiKey());
        request.addBodyParameter("secret", getApiSecret());
        request.addBodyParameter(OAuthConstants.GRANT_TYPE, OAuthConstants.AUTHORIZATION_CODE);
        request.addBodyParameter(OAuthConstants.CODE, oauthVerifier);
        return request;
    }

}
複製代碼
  • 解析返回accessToken

    注意微信跟qq返回的accessToken形式也是不同的,qq返回的形式如access_token=YOUR_ACCESS_TOKEN&expires_in=3600,而微信的則是形式如 { "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN","openid":"OPENID", "scope":"SCOPE" } 的json,因此還要重寫getAccessTokenExtractor()方法。

qq的accessToken解析直接解字符串:

@Override
    public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
        return OAuth2AccessTokenExtractor.instance();
    }
複製代碼

微信的accessToken解析的是json:

@Override
    public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
        return OAuth2AccessTokenJsonExtractor.instance();
    }
複製代碼

這樣咱們的封裝api差很少完成了,刷新續期accessToken這種就不考慮了,反正個人項目中沒用到這塊。

運行調用的例子參考github提供的連接(github.com/scribejava/…)自行調整。

4.利用accessToken訪問用戶信息

方法返回service.getAccessToken(code)一個OAuth2AccessToken對象,如今咱們能夠操做這個對象來獲取用戶我的信息進行操做了,調用相似下面的代碼:

//PROTECTED_RESOURCE_URL我的信息api
        final OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
        service.signRequest(accessToken, request);
        final Response response = service.execute(request);
        System.out.println(response.getBody());
複製代碼

若是有細心看我封裝代碼的朋友,會發現我又重寫了getBearerSignature。簡單說明一做用:

  • getBearerSignature():在獲取用戶信息時,天然要附帶上面獲取的accessToken信息和其餘判斷標識信息,那麼附帶的形式包括哪些呢?查看DefaultApi20的源碼getBearerSignature(),暫時知道的有 經過head帶上標識信息和經過url參數帶上標識信息。而qq與微信都是後者,因此咱們重寫了getBearerSignature()方法。
@Override
    public BearerSignature getBearerSignature() {
        return BearerSignatureURIQueryParameter.instance();
    }
複製代碼
相關文章
相關標籤/搜索