ScribeJava 是一個簡單的 Java 實現的 OAuth/OAuth2 庫,若是要支持qq及微信第三方登陸須要本身寫封裝協議的代碼。然而介紹如何本身封裝api的文章很是少。所以打算寫一些基礎東西,第一次寫長博客,若有不足歡迎指正。我會在在文中分享關於scribejava中qq與微信的封裝類,懶人的話能夠略過封裝代碼的說明,複製到項目中去(根據scribejava-apis版本6.0封裝,其餘版本可能會由不兼容狀況)。java
各類第三方對接oauth協議的步驟基本相同,詳細介紹及流程可參考知乎 OAuth 2 詳解,寫得簡單易懂。git
文章中提到的OAuth 定義了四種角色github
我在開發中按經常使用的Grant Type爲受權碼(Authorization Code)的方式做爲協議進行開發。另一種隱式(Grant Type: Implicit)方式不進行討論。json
再盜一下做者的受權碼的流程圖。 api
上面提到的客戶端 (Client),能夠理解爲咱們所要來第三方登陸的項目後臺。Client經過處理與其餘3個角色的關係完成最終的受權。bash
在封裝scribejava的api時,面對來自不一樣第三方登陸提供者(qq/微信),咱們所要考慮的實際就是這5步流程中1,2,4步所要發送的url如何拼接處理的問題。服務器
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>
複製代碼
廢話很少說,直接先放我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經常使用的協議規則進行拼接操做。
咱們平時點擊qq登陸會跳轉到的qq受權登陸頁面
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();
}
複製代碼
用戶經過受權頁面獲取到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/…)自行調整。
方法返回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()
方法。@Override
public BearerSignature getBearerSignature() {
return BearerSignatureURIQueryParameter.instance();
}
複製代碼