diff --git a/README.md b/README.md index 156f924fb..0418c1807 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ ScribeJava support out-of-box several HTTP clients: * XING (https://www.xing.com/) * Yahoo (https://www.yahoo.com/) * Misfit (http://misfit.com/) +* WeChat (https://weixin.qq.com/) * check the [examples folder](https://github.com/scribejava/scribejava/tree/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples) ### Small and modular diff --git a/scribejava-apis/src/main/java/com/github/scribejava/apis/WeChatApi20.java b/scribejava-apis/src/main/java/com/github/scribejava/apis/WeChatApi20.java new file mode 100644 index 000000000..5d9e8d43f --- /dev/null +++ b/scribejava-apis/src/main/java/com/github/scribejava/apis/WeChatApi20.java @@ -0,0 +1,81 @@ +package com.github.scribejava.apis; + +import com.github.scribejava.apis.service.WeChatOAuth20ServiceImpl; +import com.github.scribejava.apis.wechat.WeChatConstants; +import com.github.scribejava.apis.wechat.WeChatJsonTokenExtractor; +import com.github.scribejava.core.builder.api.DefaultApi20; +import com.github.scribejava.core.builder.api.SignatureType; +import com.github.scribejava.core.extractors.TokenExtractor; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthConfig; +import com.github.scribejava.core.model.ParameterList; +import com.github.scribejava.core.oauth.OAuth20Service; + +import java.util.Map; + +/** + * WeChat OAuth 2.0 api. + */ +public class WeChatApi20 extends DefaultApi20 { + + protected WeChatApi20() { + } + + private static class InstanceHolder { + private static final WeChatApi20 INSTANCE = new WeChatApi20(); + } + + public static WeChatApi20 instance() { + return InstanceHolder.INSTANCE; + } + + @Override + public String getAccessTokenEndpoint() { + return "https://api.weixin.qq.com/sns/oauth2/access_token"; + } + + @Override + protected String getAuthorizationBaseUrl() { + return "https://open.weixin.qq.com/connect/qrconnect"; + } + + @Override + public String getAuthorizationUrl(OAuthConfig config, Map additionalParams) { + + final ParameterList parameters = new ParameterList(additionalParams); + parameters.add(WeChatConstants.RESPONSE_TYPE, config.getResponseType()); + parameters.add(WeChatConstants.CLIENT_ID, config.getApiKey()); + + final String callback = config.getCallback(); + if (callback != null) { + parameters.add(WeChatConstants.REDIRECT_URI, callback); + } + + final String scope = config.getScope(); + if (scope != null) { + parameters.add(WeChatConstants.SCOPE, scope); + } + + final String state = config.getState(); + if (state != null) { + parameters.add(WeChatConstants.STATE, state); + } + + return parameters.appendTo(getAuthorizationBaseUrl()); + } + + @Override + public OAuth20Service createService(OAuthConfig config) { + return new WeChatOAuth20ServiceImpl(this, config); + } + + @Override + public SignatureType getSignatureType() { + return SignatureType.BEARER_URI_QUERY_PARAMETER; + } + + @Override + public TokenExtractor getAccessTokenExtractor() { + return WeChatJsonTokenExtractor.instance(); + } +} diff --git a/scribejava-apis/src/main/java/com/github/scribejava/apis/service/WeChatOAuth20ServiceImpl.java b/scribejava-apis/src/main/java/com/github/scribejava/apis/service/WeChatOAuth20ServiceImpl.java new file mode 100644 index 000000000..1ea17618a --- /dev/null +++ b/scribejava-apis/src/main/java/com/github/scribejava/apis/service/WeChatOAuth20ServiceImpl.java @@ -0,0 +1,30 @@ +package com.github.scribejava.apis.service; + +import com.github.scribejava.apis.wechat.WeChatConstants; +import com.github.scribejava.core.builder.api.DefaultApi20; +import com.github.scribejava.core.model.OAuthConfig; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.oauth.OAuth20Service; + +public class WeChatOAuth20ServiceImpl extends OAuth20Service { + + public WeChatOAuth20ServiceImpl(DefaultApi20 api, OAuthConfig config) { + super(api, config); + } + + @Override + protected OAuthRequest createAccessTokenRequest(String code) { + + final DefaultApi20 api = getApi(); + final OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint()); + final OAuthConfig config = getConfig(); + + request.addParameter(WeChatConstants.CLIENT_ID, config.getApiKey()); + request.addParameter(WeChatConstants.CLIENT_SECRET, config.getApiSecret()); + request.addParameter(WeChatConstants.CODE, code); + request.addParameter(WeChatConstants.GRANT_TYPE, WeChatConstants.AUTHORIZATION_CODE); + + return request; + } + +} diff --git a/scribejava-apis/src/main/java/com/github/scribejava/apis/wechat/WeChatConstants.java b/scribejava-apis/src/main/java/com/github/scribejava/apis/wechat/WeChatConstants.java new file mode 100644 index 000000000..01fcc0d29 --- /dev/null +++ b/scribejava-apis/src/main/java/com/github/scribejava/apis/wechat/WeChatConstants.java @@ -0,0 +1,18 @@ +package com.github.scribejava.apis.wechat; + +import com.github.scribejava.core.model.OAuthConstants; + +/** + * This class contains OAuth constants, Custom for WeChat. + */ +public interface WeChatConstants extends OAuthConstants { + + // WeChat's client_id is called appid and client_secret is called secret + String CLIENT_ID = "appid"; + String CLIENT_SECRET = "secret"; + + String LANG = "lang"; + String OPEN_ID = "openid"; + String UNION_ID = "unionid"; + +} diff --git a/scribejava-apis/src/main/java/com/github/scribejava/apis/wechat/WeChatJsonTokenExtractor.java b/scribejava-apis/src/main/java/com/github/scribejava/apis/wechat/WeChatJsonTokenExtractor.java new file mode 100644 index 000000000..941a540ba --- /dev/null +++ b/scribejava-apis/src/main/java/com/github/scribejava/apis/wechat/WeChatJsonTokenExtractor.java @@ -0,0 +1,34 @@ +package com.github.scribejava.apis.wechat; + +import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor; + +import java.util.regex.Pattern; + +/** + * additionally parses openId openid and unionId unionid. + */ +public class WeChatJsonTokenExtractor extends OAuth2AccessTokenJsonExtractor { + + private static final Pattern OPEN_ID_REGEX_PATTERN = Pattern.compile("\"openid\"\\s*:\\s*\"(\\S*?)\""); + private static final Pattern UNION_ID_REGEX_PATTERN = Pattern.compile("\"unionid\"\\s*:\\s*\"(\\S*?)\""); + + protected WeChatJsonTokenExtractor() { + } + + private static class InstanceHolder { + + private static final WeChatJsonTokenExtractor INSTANCE = new WeChatJsonTokenExtractor(); + } + + public static WeChatJsonTokenExtractor instance() { + return WeChatJsonTokenExtractor.InstanceHolder.INSTANCE; + } + + @Override + protected WeChatToken createToken(String accessToken, String tokenType, Integer expiresIn, + String refreshToken, String scope, String response) { + return new WeChatToken(accessToken, tokenType, expiresIn, refreshToken, scope, + extractParameter(response, OPEN_ID_REGEX_PATTERN, false), + extractParameter(response, UNION_ID_REGEX_PATTERN, false), response); + } +} diff --git a/scribejava-apis/src/main/java/com/github/scribejava/apis/wechat/WeChatToken.java b/scribejava-apis/src/main/java/com/github/scribejava/apis/wechat/WeChatToken.java new file mode 100644 index 000000000..f185a06a4 --- /dev/null +++ b/scribejava-apis/src/main/java/com/github/scribejava/apis/wechat/WeChatToken.java @@ -0,0 +1,74 @@ +package com.github.scribejava.apis.wechat; + +import com.github.scribejava.core.model.OAuth2AccessToken; + +import java.util.Objects; + +public class WeChatToken extends OAuth2AccessToken { + + // Unique identifier for Authorized user + private final String openId; + + // User's unified identifier for a WeChat open platform account + private final String unionId; + + public WeChatToken(String accessToken, String openId, String unionId, String rawResponse) { + this(accessToken, null, null, null, null, openId, unionId, rawResponse); + } + + public WeChatToken(String accessToken, String tokenType, Integer expiresIn, String refreshToken, String scope, + String openId, String unionId, String rawResponse) { + super(accessToken, tokenType, expiresIn, refreshToken, scope, rawResponse); + this.openId = openId; + this.unionId = unionId; + + } + + public String getOpenId() { + return openId; + } + + public String getUnionId() { + return unionId; + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 37 * hash + Objects.hashCode(openId); + hash = 37 * hash + Objects.hashCode(unionId); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + if (!super.equals(obj)) { + return false; + } + if (!Objects.equals(openId, ((WeChatToken) obj).getOpenId())) { + return false; + } + return Objects.equals(unionId, ((WeChatToken) obj).getUnionId()); + } + + @Override + public String toString() { + return "WeChatToken{" + + "access_token=" + getAccessToken() + + ", expires_in=" + getExpiresIn() + + ", refresh_token=" + getRefreshToken() + + ", openid=" + openId + + ", scope=" + getScope() + + ", unionid=" + unionId + + '}'; + } +} diff --git a/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/WeChat20Example.java b/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/WeChat20Example.java new file mode 100644 index 000000000..93067d128 --- /dev/null +++ b/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/WeChat20Example.java @@ -0,0 +1,78 @@ +package com.github.scribejava.apis.examples; + +import com.github.scribejava.apis.WeChatApi20; +import com.github.scribejava.apis.wechat.WeChatConstants; +import com.github.scribejava.apis.wechat.WeChatToken; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; + +import java.io.IOException; +import java.util.Scanner; +import java.util.concurrent.ExecutionException; + +public final class WeChat20Example { + + private static final String NETWORK_NAME = "WeChat"; + private static final String PROTECTED_RESOURCE_URL = "https://api.weixin.qq.com/sns/userinfo"; + + private WeChat20Example() { + } + + public static void main(String... args) throws IOException, InterruptedException, ExecutionException { + // Replace these with your own api key and secret + final String apiKey = "your_appid"; + final String apiSecret = "your_secret"; + final OAuth20Service service = new ServiceBuilder() + .apiKey(apiKey) + .apiSecret(apiSecret) + .callback("http://your.site.com/callback") // your application's authorized callback field + .scope("snsapi_login") + .state("login") + .build(WeChatApi20.instance()); + final Scanner in = new Scanner(System.in); + + System.out.println("=== " + NETWORK_NAME + "'s OAuth Workflow ==="); + System.out.println(); + + // Obtain the Authorization URL + System.out.println("Fetching the Authorization URL..."); + final String authorizationUrl = service.getAuthorizationUrl(); + System.out.println("Got the Authorization URL!"); + System.out.println("Now go and authorize ScribeJava here:"); + System.out.println(authorizationUrl); + System.out.println("And paste the authorization code here"); + System.out.print(">>"); + final String code = in.nextLine(); + System.out.println(); + + // Trade the Request Token and Verifier for the Access Token + System.out.println("Trading the Request Token for an Access Token..."); + final OAuth2AccessToken accessToken = service.getAccessToken(code); + System.out.println("Got the Access Token!"); + System.out.println("(if your curious it looks like this: " + accessToken + + ", 'rawResponse'='" + accessToken.getRawResponse() + "')"); + System.out.println(); + + // Now let's go and ask for a protected resource! + System.out.println("Now we're going to access a protected resource..."); + + final OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL); + request.addQuerystringParameter(WeChatConstants.OPEN_ID, ((WeChatToken) accessToken).getOpenId()); + request.addQuerystringParameter(WeChatConstants.LANG, "zh_CN"); + + service.signRequest(accessToken, request); + final Response response = service.execute(request); + System.out.println("Got it! Lets see what we found..."); + System.out.println(); + System.out.println(response.getCode()); + System.out.println(response.getBody()); + + System.out.println(); + System.out.println("Thats it man! Go and build something awesome with ScribeJava! :)"); + + } +}