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();
    }
复制代码
相关文章
相关标签/搜索