diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java index 8a843e94b..182602954 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java @@ -128,4 +128,12 @@ public class WxConsts { /** 弹出地理位置选择器 */ public static final String LOCATION_SELECT = "location_select"; + /////////////////////// + // oauth2网页授权的scope + /////////////////////// + /** 不弹出授权页面,直接跳转,只能获取用户openid */ + public static final String OAUTH2_SCOPE_BASE = "snsapi_base"; + /** 弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息 */ + public static final String OAUTH2_SCOPE_USER_INFO = "snsapi_userinfo"; + } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/URIUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/URIUtil.java new file mode 100644 index 000000000..bb671eb1c --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/URIUtil.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.common.util.http; + +import me.chanjar.weixin.common.util.StringUtils; + +import java.io.UnsupportedEncodingException; + +public class URIUtil { + + private static final String ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()"; + + public static String encodeURIComponent(String input) { + if (StringUtils.isEmpty(input)) { + return input; + } + + int l = input.length(); + StringBuilder o = new StringBuilder(l * 3); + try { + for (int i = 0; i < l; i++) { + String e = input.substring(i, i + 1); + if (ALLOWED_CHARS.indexOf(e) == -1) { + byte[] b = e.getBytes("utf-8"); + o.append(getHex(b)); + continue; + } + o.append(e); + } + return o.toString(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return input; + } + + private static String getHex(byte buf[]) { + StringBuilder o = new StringBuilder(buf.length * 3); + for (int i = 0; i < buf.length; i++) { + int n = (int) buf[i] & 0xff; + o.append("%"); + if (n < 0x10) { + o.append("0"); + } + o.append(Long.toString(n, 16).toUpperCase()); + } + return o.toString(); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java index e7ee779cc..a2ef9d6a8 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java @@ -25,6 +25,8 @@ public interface WxMpConfigStorage { public int getExpiresIn(); + public String getOauth2redirectUrl(); + public String getHttp_proxy_host(); public int getHttp_proxy_port(); diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java index d0f9c33d6..0f6dd7887 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java @@ -20,6 +20,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { protected int http_proxy_port; protected String http_proxy_username; protected String http_proxy_password; + protected String oauth2redirectUrl; public void updateAccessToken(WxAccessToken accessToken) { updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); @@ -78,6 +79,15 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { this.expiresIn = expiresIn; } + @Override + public String getOauth2redirectUrl() { + return this.oauth2redirectUrl; + } + + public void setOauth2redirectUrl(String oauth2redirectUrl) { + this.oauth2redirectUrl = oauth2redirectUrl; + } + public String getHttp_proxy_host() { return http_proxy_host; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java index daaa062fa..8fc70e8e3 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java @@ -329,6 +329,54 @@ public interface WxMpService { */ WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException; + /** + *
+   * 构造oauth2授权的url连接
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息
+   * 
+ * @param scope + * @param state + * @return code + */ + public String oauth2buildAuthorizationUrl(String scope, String state); + + /** + *
+   * 用code换取oauth2的access token
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息
+   * 
+ * @param code + * @return + */ + public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException; + + /** + *
+   * 刷新oauth2的access token
+   * 
+ * @param refreshToken + * @return + */ + public WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException; + + /** + *
+   * 用oauth2获取用户信息, 当前面引导授权时的scope是snsapi_userinfo的时候才可以
+   * 
+ * @param oAuth2AccessToken + * @param lang zh_CN, zh_TW, en + */ + public WxMpUser oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException; + + /** + *
+   * 验证oauth2的access token是否有效
+   * 
+ * @param oAuth2AccessToken + * @return + */ + public boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken); + /** * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 * @param url diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java index e55f261f1..5e07b7c2f 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java @@ -27,11 +27,13 @@ import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIUtils; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import javax.print.DocFlavor; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -297,6 +299,98 @@ public class WxMpServiceImpl implements WxMpService { return WxMpSemanticQueryResult.fromJson(responseContent); } + @Override + public String oauth2buildAuthorizationUrl(String scope, String state) { + String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" ; + url += "appid=" + wxMpConfigStorage.getAppId(); + url += "&redirect_uri=" + URIUtil.encodeURIComponent(wxMpConfigStorage.getOauth2redirectUrl()); + url += "&response_type=code"; + url += "&scope=" + scope; + if (state != null) { + url += "&state=" + state; + } + url += "#wechat_redirect"; + return url; + } + + @Override + public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException { + String url = "https://api.weixin.qq.com/sns/oauth2/access_token?"; + url += "appid=" + wxMpConfigStorage.getAppId(); + url += "&secret=" + wxMpConfigStorage.getSecret(); + url += "&code=" + code; + url += "&grant_type=authorization_code"; + + try { + RequestExecutor executor = new SimpleGetRequestExecutor(); + String responseText = executor.execute(getHttpclient(), httpProxy, url, null); + return WxMpOAuth2AccessToken.fromJson(responseText); + } catch (ClientProtocolException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException { + String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?"; + url += "appid=" + wxMpConfigStorage.getAppId(); + url += "&grant_type=refresh_token"; + url += "&refresh_token=" + refreshToken; + + try { + RequestExecutor executor = new SimpleGetRequestExecutor(); + String responseText = executor.execute(getHttpclient(), httpProxy, url, null); + return WxMpOAuth2AccessToken.fromJson(responseText); + } catch (ClientProtocolException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public WxMpUser oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException { + String url = "https://api.weixin.qq.com/sns/userinfo?"; + url += "access_token=" + oAuth2AccessToken.getAccessToken(); + url += "&openid=" + oAuth2AccessToken.getOpenId(); + if (lang == null) { + url += "&lang=zh_CN"; + } else { + url += "&lang=" + lang; + } + + try { + RequestExecutor executor = new SimpleGetRequestExecutor(); + String responseText = executor.execute(getHttpclient(), httpProxy, url, null); + return WxMpUser.fromJson(responseText); + } catch (ClientProtocolException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken) { + String url = "https://api.weixin.qq.com/sns/auth?"; + url += "access_token=" + oAuth2AccessToken; + url += "&openid=" + oAuth2AccessToken.getOpenId(); + + try { + RequestExecutor executor = new SimpleGetRequestExecutor(); + executor.execute(getHttpclient(), httpProxy, url, null); + } catch (ClientProtocolException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (WxErrorException e) { + return false; + } + return true; + } + public String get(String url, String queryParam) throws WxErrorException { return execute(new SimpleGetRequestExecutor(), url, queryParam); } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java new file mode 100644 index 000000000..01c5b99cd --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java @@ -0,0 +1,64 @@ +package me.chanjar.weixin.mp.bean.result; + +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +/** + * Created by qianjia on 14/11/26. + */ +public class WxMpOAuth2AccessToken { + + private String accessToken; + + private int expiresIn = -1; + + private String refreshToken; + + private String openId; + + private String scope; + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getOpenId() { + return openId; + } + + public void setOpenId(String openId) { + this.openId = openId; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public int getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(int expiresIn) { + this.expiresIn = expiresIn; + } + + public static WxMpOAuth2AccessToken fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpOAuth2AccessToken.class); + } + +}