diff --git a/docs/oauth/wechat.md b/docs/oauth/wechat.md index b579bf5..bd67ec7 100644 --- a/docs/oauth/wechat.md +++ b/docs/oauth/wechat.md @@ -79,7 +79,7 @@ String authorizeUrl = authRequest.authorize(AuthStateUtils.createState()); ```java import me.zhyd.oauth.config.AuthConfig; -import me.zhyd.oauth.request.AuthWeChatRequest; +import me.zhyd.oauth.request.AuthWeChatOpenRequest; import me.zhyd.oauth.request.AuthRequest; import me.zhyd.oauth.utils.AuthStateUtils; import org.springframework.web.bind.annotation.PathVariable; @@ -107,7 +107,7 @@ public class RestAuthController { } private AuthRequest getAuthRequest() { - return new AuthWeChatRequest(AuthConfig.builder() + return new AuthWeChatOpenRequest(AuthConfig.builder() .clientId("Client ID") .clientSecret("Client Secret") .redirectUri("https://www.zhyd.me/oauth/callback/wechat") diff --git a/pom.xml b/pom.xml index a4f365c..9b0f2f4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.zhyd.oauth JustAuth - 1.13.1 + 1.13.2-SNAPSHOT JustAuth https://gitee.com/yadong.zhang/JustAuth diff --git a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java index a029f66..09f67d8 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java +++ b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java @@ -240,9 +240,9 @@ public enum AuthDefaultSource implements AuthSource { } }, /** - * 微信 + * 微信开放平台 */ - WECHAT { + WECHAT_OPEN { @Override public String authorize() { return "https://open.weixin.qq.com/connect/qrconnect"; @@ -263,6 +263,30 @@ public enum AuthDefaultSource implements AuthSource { return "https://api.weixin.qq.com/sns/oauth2/refresh_token"; } }, + /** + * 微信公众平台 + */ + WECHAT_MP { + @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 refresh() { + return "https://api.weixin.qq.com/sns/oauth2/refresh_token"; + } + }, /** * 淘宝 */ diff --git a/src/main/java/me/zhyd/oauth/request/AuthWeChatMpRequest.java b/src/main/java/me/zhyd/oauth/request/AuthWeChatMpRequest.java new file mode 100644 index 0000000..ac67094 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthWeChatMpRequest.java @@ -0,0 +1,187 @@ +package me.zhyd.oauth.request; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthDefaultSource; +import me.zhyd.oauth.enums.AuthResponseStatus; +import me.zhyd.oauth.enums.AuthUserGender; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.utils.GlobalAuthUtil; +import me.zhyd.oauth.utils.UrlBuilder; + +/** + * 微信公众平台登录 + * + * @author yangkai.shen (https://xkcoding.com) + * @since 1.1.0 + */ +public class AuthWeChatMpRequest extends AuthDefaultRequest { + public AuthWeChatMpRequest(AuthConfig config) { + super(config, AuthDefaultSource.WECHAT_MP); + } + + public AuthWeChatMpRequest(AuthConfig config, AuthStateCache authStateCache) { + super(config, AuthDefaultSource.WECHAT_MP, authStateCache); + } + + /** + * 微信的特殊性,此时返回的信息同时包含 openid 和 access_token + * + * @param authCallback 回调返回的参数 + * @return 所有信息 + */ + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + return this.getToken(accessTokenUrl(authCallback.getCode())); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + String openId = authToken.getOpenId(); + + HttpResponse response = doGetUserInfo(authToken); + JSONObject object = JSONObject.parseObject(response.body()); + + this.checkResponse(object); + + String location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city")); + + if (object.containsKey("unionid")) { + authToken.setUnionId(object.getString("unionid")); + } + + AuthUserGender sex; + switch (object.getString("sex")) { + case "1": + sex = AuthUserGender.MALE; + break; + case "2": + sex = AuthUserGender.FEMALE; + break; + default: + sex = AuthUserGender.UNKNOWN; + } + + return AuthUser.builder() + .username(object.getString("nickname")) + .nickname(object.getString("nickname")) + .avatar(object.getString("headimgurl")) + .location(location) + .uuid(openId) + .gender(sex) + .token(authToken) + .source(source.toString()) + .build(); + } + + @Override + public AuthResponse refresh(AuthToken oldToken) { + return AuthResponse.builder() + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken()))) + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.containsKey("errcode")) { + throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg")); + } + } + + /** + * 获取token,适用于获取access_token和刷新token + * + * @param accessTokenUrl 实际请求token的地址 + * @return token对象 + */ + private AuthToken getToken(String accessTokenUrl) { + HttpResponse response = HttpRequest.get(accessTokenUrl).execute(); + JSONObject accessTokenObject = JSONObject.parseObject(response.body()); + + this.checkResponse(accessTokenObject); + + return AuthToken.builder() + .accessToken(accessTokenObject.getString("access_token")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .openId(accessTokenObject.getString("openid")) + .scope(accessTokenObject.getString("scope")) + .build(); + } + + /** + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} + * + * @param state state 验证授权流程的参数,可以防止csrf + * @return 返回授权地址 + * @since 1.9.3 + */ + @Override + public String authorize(String state) { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("appid", config.getClientId()) + .queryParam("redirect_uri", GlobalAuthUtil.urlEncode(config.getRedirectUri())) + .queryParam("response_type", "code") + .queryParam("scope", "snsapi_userinfo") + .queryParam("state", getRealState(state).concat("#wechat_redirect")) + .build(); + } + + /** + * 返回获取accessToken的url + * + * @param code 授权码 + * @return 返回获取accessToken的url + */ + @Override + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("appid", config.getClientId()) + .queryParam("secret", config.getClientSecret()) + .queryParam("code", code) + .queryParam("grant_type", "authorization_code") + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken 用户授权后的token + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("openid", authToken.getOpenId()) + .queryParam("lang", "zh_CN") + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param refreshToken getAccessToken方法返回的refreshToken + * @return 返回获取userInfo的url + */ + @Override + protected String refreshTokenUrl(String refreshToken) { + return UrlBuilder.fromBaseUrl(source.refresh()) + .queryParam("appid", config.getClientId()) + .queryParam("grant_type", "refresh_token") + .queryParam("refresh_token", refreshToken) + .build(); + } +} diff --git a/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java b/src/main/java/me/zhyd/oauth/request/AuthWeChatOpenRequest.java similarity index 93% rename from src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java rename to src/main/java/me/zhyd/oauth/request/AuthWeChatOpenRequest.java index 1e78d58..d46a1fc 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthWeChatOpenRequest.java @@ -16,18 +16,18 @@ import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.utils.UrlBuilder; /** - * 微信登录 + * 微信开放平台登录 * * @author yangkai.shen (https://xkcoding.com) * @since 1.1.0 */ -public class AuthWeChatRequest extends AuthDefaultRequest { - public AuthWeChatRequest(AuthConfig config) { - super(config, AuthDefaultSource.WECHAT); +public class AuthWeChatOpenRequest extends AuthDefaultRequest { + public AuthWeChatOpenRequest(AuthConfig config) { + super(config, AuthDefaultSource.WECHAT_OPEN); } - public AuthWeChatRequest(AuthConfig config, AuthStateCache authStateCache) { - super(config, AuthDefaultSource.WECHAT, authStateCache); + public AuthWeChatOpenRequest(AuthConfig config, AuthStateCache authStateCache) { + super(config, AuthDefaultSource.WECHAT_OPEN, authStateCache); } /** diff --git a/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java b/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java index 86b6789..a2a8a69 100644 --- a/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java +++ b/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java @@ -2,7 +2,7 @@ package me.zhyd.oauth.utils; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthDefaultSource; -import me.zhyd.oauth.request.AuthWeChatRequest; +import me.zhyd.oauth.request.AuthWeChatOpenRequest; import org.junit.Assert; import org.junit.Test; @@ -22,7 +22,7 @@ public class UrlBuilderTest { .clientSecret("secret-110110110") .redirectUri("https://xkcoding.com") .build(); - String build = UrlBuilder.fromBaseUrl(AuthDefaultSource.WECHAT.authorize()) + String build = UrlBuilder.fromBaseUrl(AuthDefaultSource.WECHAT_OPEN.authorize()) .queryParam("appid", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) .queryParam("response_type", "code") @@ -30,7 +30,7 @@ public class UrlBuilderTest { .queryParam("state", "") .build(false); System.out.println(build); - AuthWeChatRequest request = new AuthWeChatRequest(config); + AuthWeChatOpenRequest request = new AuthWeChatOpenRequest(config); String authorize = request.authorize("state"); System.out.println(authorize); }