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();
}
复制代码