diff --git a/pom.xml b/pom.xml index 94f3fd5760d2c02f2789dc308b6730198b4d5c20..f0e6846254b7db5df699a252eb940fe6208ede12 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,11 @@ yadong.zhang0415@gmail.com https://www.zhyd.me + + Yangkai.Shen + shenyangkai1994@gmail.com + https://xkcoding.com + @@ -46,6 +51,7 @@ 4.11 1.2.44 1.28.0 + 27.1-jre 3.7.4.ALL diff --git a/src/main/java/me/zhyd/oauth/consts/ApiUrl.java b/src/main/java/me/zhyd/oauth/consts/ApiUrl.java index f968e550bd3470901ffce2795b10bc8c9fe34f9a..34fb1d800a3dd6a397e1937c9c9ee812bcf956a1 100644 --- a/src/main/java/me/zhyd/oauth/consts/ApiUrl.java +++ b/src/main/java/me/zhyd/oauth/consts/ApiUrl.java @@ -329,6 +329,35 @@ public enum ApiUrl { public String refresh() { throw new AuthException(ResponseStatus.UNSUPPORTED); } + }, + /** + * 微信 + */ + WECHAT { + @Override + public String authorize() { + return "https://open.weixin.qq.com/connect/oauth2/authorize"; + } + + @Override + public String accessToken() { + return "https://api.weixin.qq.com/sns/oauth2/access_token"; + } + + @Override + public String userInfo() { + return "https://api.weixin.qq.com/sns/userinfo"; + } + + @Override + public String revoke() { + throw new AuthException(ResponseStatus.UNSUPPORTED); + } + + @Override + public String refresh() { + return "https://api.weixin.qq.com/sns/oauth2/refresh_token"; + } }; public abstract String authorize(); diff --git a/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java b/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..257afaa37c8e25143f9e83f470158080ec78d503 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java @@ -0,0 +1,106 @@ +package me.zhyd.oauth.request; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthSource; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.model.AuthUserGender; +import me.zhyd.oauth.utils.UrlBuilder; + +/** + *

+ * 微信登录 + *

+ * + * @package: me.zhyd.oauth.request + * @description: 微信登录 + * @author: yangkai.shen + * @date: Created in 2019-05-17 11:11 + * @copyright: Copyright (c) 2019 + * @version: V1.0 + * @modified: yangkai.shen + */ +public class AuthWeChatRequest extends BaseAuthRequest { + public AuthWeChatRequest(AuthConfig config) { + super(config, AuthSource.WECHAT); + } + + /** + * 微信的特殊性,此时返回的信息同时包含 openid 和 access_token + * + * @param code 授权码 + * @return 所有信息 + */ + @Override + protected String getAccessToken(String code) { + String accessTokenUrl = UrlBuilder.getWeChatAccessTokenUrl(config.getClientId(), config.getClientSecret(), code); + HttpResponse response = HttpRequest.get(accessTokenUrl).execute(); + JSONObject accessTokenObject = JSONObject.parseObject(response.body()); + if (!accessTokenObject.containsKey("access_token") || !accessTokenObject.containsKey("openid") || !accessTokenObject + .containsKey("refresh_token")) { + throw new AuthException("Unable to get access_token or openid or refresh_token from wechat using code [" + code + "]"); + } + return response.body(); + } + + @Override + protected AuthUser getUserInfo(String accessToken) { + String token = this.getToken(accessToken); + String openId = this.getOpenId(accessToken); + + HttpResponse response = HttpRequest.get(UrlBuilder.getWeChatUserInfoUrl(token, openId)).execute(); + JSONObject object = JSONObject.parseObject(response.body()); + if (object.containsKey("errcode")) { + throw new AuthException(object.getString("errmsg")); + } + + return AuthUser.builder() + .username(object.getString("nickname")) + .nickname(object.getString("nickname")) + .avatar(object.getString("headimgurl")) + .location(object.getString("country") + "-" + object.getString("province") + "-" + object.getString("city")) + .gender(AuthUserGender.getRealGender(object.getString("sex"))) + .accessToken(accessToken) + .source(AuthSource.WECHAT) + .build(); + } + + /** + * 刷新access token (续期) + * + * @param accessToken 登录成功后返回的accessToken + * @return AuthResponse + */ + @Override + public AuthResponse refresh(String accessToken) { + String refreshToken = getRefreshToken(accessToken); + HttpResponse response = HttpRequest.get(UrlBuilder.getWeChatRefreshUrl(config.getClientId(), refreshToken)) + .execute(); + + JSONObject object = JSONObject.parseObject(response.body()); + if (object.containsKey("errcode")) { + throw new AuthException(object.getString("errmsg")); + } + + return AuthResponse.builder().data(object).build(); + } + + private String getRefreshToken(String accessToken) { + JSONObject accessTokenObject = JSONObject.parseObject(accessToken); + return accessTokenObject.getString("refresh_token"); + } + + private String getOpenId(String accessToken) { + JSONObject accessTokenObject = JSONObject.parseObject(accessToken); + return accessTokenObject.getString("openid"); + } + + private String getToken(String accessToken) { + JSONObject accessTokenObject = JSONObject.parseObject(accessToken); + return accessTokenObject.getString("access_token"); + } +} diff --git a/src/main/java/me/zhyd/oauth/request/BaseAuthRequest.java b/src/main/java/me/zhyd/oauth/request/BaseAuthRequest.java index a661e495b746dd962e2a04854ba28c211926bc53..597328ad67de27f4ebedd1aa53d493c0e8310843 100644 --- a/src/main/java/me/zhyd/oauth/request/BaseAuthRequest.java +++ b/src/main/java/me/zhyd/oauth/request/BaseAuthRequest.java @@ -79,6 +79,7 @@ public abstract class BaseAuthRequest implements AuthRequest { authorizeUrl = UrlBuilder.getQqAuthorizeUrl(config.getClientId(), config.getRedirectUri()); break; case WECHAT: + authorizeUrl = UrlBuilder.getWeChatAuthorizeUrl(config.getClientId(), config.getRedirectUri()); break; case GOOGLE: break; diff --git a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java index 44d091fa48cb843beeb84af7abd733d0e5c42bd1..37392b21dec89d3bc954824d24b438decf0cd6cf 100644 --- a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java +++ b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java @@ -56,6 +56,11 @@ public class UrlBuilder { private static final String QQ_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&state={3}"; private static final String QQ_OPENID_PATTERN = "{0}?access_token={1}"; + private static final String WECHAT_AUTHORIZE_PATTERN = "{0}?appid={1}&redirect_uri={2}&response_type=code&scope=snsapi_userinfo#wechat_redirect"; + private static final String WECHAT_ACCESS_TOKEN_PATTERN = "{0}?appid={1}&secret={2}&code={3}&grant_type=authorization_code"; + private static final String WECHAT_REFRESH_TOKEN_PATTERN = "{0}?appid={1}&grant_type=refresh_token&refresh_token={2}"; + private static final String WECHAT_USER_INFO_PATTERN = "{0}?access_token={1}&openid={2}&lang=zh_CN"; + /** * 获取githubtoken的接口地址 * @@ -415,4 +420,49 @@ public class UrlBuilder { public static String getAlipayAuthorizeUrl(String clientId, String redirectUrl) { return MessageFormat.format(ALIPAY_AUTHORIZE_PATTERN, ApiUrl.ALIPAY.authorize(), clientId, redirectUrl); } + + /** + * 获取微信 授权地址 + * + * @param clientId 微信应用的appid + * @param redirectUrl 微信应用授权成功后的回调地址 + * @return full url + */ + public static String getWeChatAuthorizeUrl(String clientId, String redirectUrl) { + return MessageFormat.format(WECHAT_AUTHORIZE_PATTERN, ApiUrl.WECHAT.authorize(), clientId, redirectUrl); + } + + /** + * 获取微信 token的接口地址 + * + * @param clientId 微信应用的appid + * @param clientSecret 微信应用的secret + * @param code 微信授权前的code,用来换token + * @return full url + */ + public static String getWeChatAccessTokenUrl(String clientId, String clientSecret, String code) { + return MessageFormat.format(WECHAT_ACCESS_TOKEN_PATTERN, ApiUrl.WECHAT.accessToken(), clientId, clientSecret, code); + } + + /** + * 获取微信 用户详情的接口地址 + * + * @param token 微信应用返回的 access token + * @param openId 微信应用返回的openId + * @return full url + */ + public static String getWeChatUserInfoUrl(String token, String openId) { + return MessageFormat.format(WECHAT_USER_INFO_PATTERN, ApiUrl.WECHAT.userInfo(), token, openId); + } + + /** + * 获取微信 刷新令牌 地址 + * + * @param clientId 微信应用的appid + * @param refreshToken 微信应用返回的刷新token + * @return full url + */ + public static String getWeChatRefreshUrl(String clientId, String refreshToken) { + return MessageFormat.format(WECHAT_REFRESH_TOKEN_PATTERN, ApiUrl.WECHAT.refresh(), clientId, refreshToken); + } } diff --git a/src/test/java/me/zhyd/oauth/AuthRequestTest.java b/src/test/java/me/zhyd/oauth/AuthRequestTest.java index b027e97ab75b897265125c44ae092377a365079e..8b67f23d424f74cf3ff6dce3b6721527f1c8c868 100644 --- a/src/test/java/me/zhyd/oauth/AuthRequestTest.java +++ b/src/test/java/me/zhyd/oauth/AuthRequestTest.java @@ -1,6 +1,7 @@ package me.zhyd.oauth; import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.model.AuthResponse; import me.zhyd.oauth.request.*; import org.junit.Test; @@ -114,4 +115,17 @@ public class AuthRequestTest { // 授权登录后会返回一个code,用这个code进行登录 authRequest.login("code"); } + + @Test + public void wechatTest() { + AuthRequest authRequest = new AuthWeChatRequest(AuthConfig.builder() + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); + // 返回授权页面,可自行调整 + String url = authRequest.authorize(); + // 授权登录后会返回一个code,用这个code进行登录 + AuthResponse login = authRequest.login("code"); + } }