diff --git a/src/main/java/me/zhyd/oauth/config/AuthConfig.java b/src/main/java/me/zhyd/oauth/config/AuthConfig.java index deada8904ac748d5b980696eb5cfe3a229ab07b6..00f0b95540005d06c676dcab42d94e2e77f91d3e 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthConfig.java +++ b/src/main/java/me/zhyd/oauth/config/AuthConfig.java @@ -50,4 +50,9 @@ public class AuthConfig { * 1.9.0版本新增参数 */ private String stackOverflowKey; + + /** + * 企业微信,授权方的网页应用ID + */ + private String agentId; } diff --git a/src/main/java/me/zhyd/oauth/config/AuthSource.java b/src/main/java/me/zhyd/oauth/config/AuthSource.java index 00e0ec2114bd18f895e3bd2827ba46a6ef0f726d..2f5784725ce9031dc29180189c47598585326367 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthSource.java +++ b/src/main/java/me/zhyd/oauth/config/AuthSource.java @@ -1,7 +1,7 @@ package me.zhyd.oauth.config; -import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.enums.AuthResponseStatus; +import me.zhyd.oauth.exception.AuthException; /** * 各api需要的url, 用枚举类分平台类型管理 @@ -518,6 +518,26 @@ public enum AuthSource { public String userInfo() { return "https://api.stackexchange.com/2.2/me"; } + }, + + /** + * 企业微信 + */ + WECHAT_ENTERPRISE { + @Override + public String authorize() { + return "https://open.work.weixin.qq.com/wwopen/sso/qrConnect"; + } + + @Override + public String accessToken() { + return "https://qyapi.weixin.qq.com/cgi-bin/gettoken"; + } + + @Override + public String userInfo() { + return "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"; + } }; /** diff --git a/src/main/java/me/zhyd/oauth/model/AuthToken.java b/src/main/java/me/zhyd/oauth/model/AuthToken.java index 805a196c3c93b2149c07c5c44265e3d998fedea7..f1600c66d5476877f475a1f5b1f78a963addc8ab 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthToken.java +++ b/src/main/java/me/zhyd/oauth/model/AuthToken.java @@ -1,7 +1,6 @@ package me.zhyd.oauth.model; import lombok.Builder; -import lombok.Data; import lombok.Getter; import lombok.Setter; @@ -36,4 +35,9 @@ public class AuthToken { private String macAlgorithm; private String macKey; + /** + * 企业微信附带属性 + */ + private String code; + } diff --git a/src/main/java/me/zhyd/oauth/request/AuthWeChatEnterpriseRequest.java b/src/main/java/me/zhyd/oauth/request/AuthWeChatEnterpriseRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..8bd81c6f18521eac1ad23352f2322d7cdf7bc46c --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthWeChatEnterpriseRequest.java @@ -0,0 +1,161 @@ +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.AuthSource; +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.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.utils.UrlBuilder; + +/** + *

+ * 企业微信登录 + *

+ * + * @author yangkai.shen (https://xkcoding.com) + * @date Created in 2019-08-06 14:11 + */ +public class AuthWeChatEnterpriseRequest extends AuthDefaultRequest { + public AuthWeChatEnterpriseRequest(AuthConfig config) { + super(config, AuthSource.WECHAT_ENTERPRISE); + } + + public AuthWeChatEnterpriseRequest(AuthConfig config, AuthStateCache authStateCache) { + super(config, AuthSource.WECHAT_ENTERPRISE, authStateCache); + } + + /** + * 微信的特殊性,此时返回的信息同时包含 openid 和 access_token + * + * @param authCallback 回调返回的参数 + * @return 所有信息 + */ + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + HttpResponse response = doGetAuthorizationCode(accessTokenUrl(authCallback.getCode())); + + JSONObject object = this.checkResponse(response); + + return AuthToken.builder() + .accessToken(object.getString("access_token")) + .expireIn(object.getIntValue("expires_in")) + .code(authCallback.getCode()) + .build(); + } + + private JSONObject checkResponse(HttpResponse response) { + JSONObject object = JSONObject.parseObject(response.body()); + + if (object.containsKey("errcode") && object.getIntValue("errcode") != 0) { + throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg")); + } + + return object; + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + HttpResponse response = doGetUserInfo(authToken); + JSONObject object = this.checkResponse(response); + + // 返回 UserId + if (object.containsKey("UserId")) { + String userId = object.getString("UserId"); + HttpResponse userDetailResponse = getUserDetail(authToken.getAccessToken(), userId); + JSONObject userDetail = this.checkResponse(userDetailResponse); + + String gender = userDetail.getString("gender"); + if (gender.equals("0")) { + gender = null; + } else if (gender.equals("1")) { + gender = "1"; + } else if (gender.equals("2")) { + gender = "0"; + } + + return AuthUser.builder() + .username(userDetail.getString("name")) + .nickname(userDetail.getString("alias")) + .avatar(userDetail.getString("avatar")) + .location(userDetail.getString("address")) + .email(userDetail.getString("email")) + .uuid(userId) + .gender(AuthUserGender.getRealGender(gender)) + .token(authToken) + .source(source) + .build(); + } + // 返回 OpenId 或其他,均代表非当前企业用户,不支持 + else { + throw new AuthException(AuthResponseStatus.UNIDENTIFIED_PLATFORM); + } + } + + /** + * 返回带{@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("agentid", config.getAgentId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(state)) + .build(); + } + + /** + * 返回获取accessToken的url + * + * @param code 授权码 + * @return 返回获取accessToken的url + */ + @Override + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("corpid", config.getClientId()) + .queryParam("corpsecret", config.getClientSecret()) + .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("code", authToken.getCode()) + .build(); + } + + /** + * 用户详情 + * + * @param accessToken accessToken + * @param userId 企业内用户id + * @return 用户详情 + */ + private HttpResponse getUserDetail(String accessToken, String userId) { + String userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get") + .queryParam("access_token", accessToken) + .queryParam("userid", userId) + .build(); + return HttpRequest.get(userDetailUrl).execute(); + } + +} diff --git a/src/main/java/me/zhyd/oauth/utils/AuthChecker.java b/src/main/java/me/zhyd/oauth/utils/AuthChecker.java index 770f960f0865de240cd5a5299cc42a3930472ff1..03135469d45c7cb725c42266cb3679c53b6f746f 100644 --- a/src/main/java/me/zhyd/oauth/utils/AuthChecker.java +++ b/src/main/java/me/zhyd/oauth/utils/AuthChecker.java @@ -29,6 +29,9 @@ public class AuthChecker { if (isSupported && AuthSource.STACK_OVERFLOW == source) { isSupported = StringUtils.isNotEmpty(config.getStackOverflowKey()); } + if (isSupported && AuthSource.WECHAT_ENTERPRISE == source){ + isSupported = StringUtils.isNotEmpty(config.getAgentId()); + } return isSupported; }