From 64e109851970a72c94089d78e6c73d61c9cca314 Mon Sep 17 00:00:00 2001 From: "yadong.zhang" Date: Mon, 2 Sep 2024 00:24:05 +0800 Subject: [PATCH] =?UTF-8?q?:hankey:=20=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=89=88?= =?UTF-8?q?`=E9=92=89=E9=92=89=E6=89=AB=E7=A0=81`=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOGS.md | 3 +- .../java/me/zhyd/oauth/config/AuthConfig.java | 40 ++++++- .../zhyd/oauth/config/AuthDefaultSource.java | 24 ++++ .../oauth/enums/scope/AuthDingTalkScope.java | 33 ++++++ .../java/me/zhyd/oauth/model/AuthToken.java | 7 ++ .../oauth/request/AuthDingTalkV2Request.java | 108 ++++++++++++++++++ 6 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 src/main/java/me/zhyd/oauth/enums/scope/AuthDingTalkScope.java create mode 100644 src/main/java/me/zhyd/oauth/request/AuthDingTalkV2Request.java diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 40a76ba..aa90ed5 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,11 +1,12 @@ ## 1.16.7 ### 2024/08/03 -: + - 新增 - 添加`appleid`社交登录能力。 [Github#192](https://github.com/justauth/JustAuth/pull/192) - 添加`figma`社交登录能力。 [Gitee#41](https://gitee.com/yadong.zhang/JustAuth/pulls/41) - 添加新版`企业微信扫码`登录能力。 [Github Issue#165](https://github.com/justauth/JustAuth/issues/165) + - 添加新版`钉钉扫码`登录能力。 [Gitee Issue#I73FZL](https://gitee.com/yadong.zhang/JustAuth/issues/I73FZL) - 添加新版`华为`登录能力,原`AuthHuaweiRequest`会在后面版本被弃用,如有使用,请切换到`AuthHuaweiV3Request` - 新增微信小程序授权登录 - 优化 diff --git a/src/main/java/me/zhyd/oauth/config/AuthConfig.java b/src/main/java/me/zhyd/oauth/config/AuthConfig.java index 6dbb4b0..74e192a 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthConfig.java +++ b/src/main/java/me/zhyd/oauth/config/AuthConfig.java @@ -209,9 +209,47 @@ public class AuthConfig { private String loginType = "CorpApp"; /** - * 语言编码 + * 企业微信平台的语言编码 * * @since 1.16.7 */ private String lang = "zh"; + + /** + * 钉钉平台参数:控制输出特定类型的组织列表,org_type=management 表示只输出有管理权限的组织。 + * + * scope包含corpid时该参数存在意义。 + * + * @see https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug + * @since 1.16.7 + */ + private String dingTalkOrgType; + + /** + * 钉钉平台参数:用于指定用户需要选择的组织。 + * + * scope包含corpid时该参数存在意义。传入的corpId需要是当前用户所在的组织。 + * + * @see https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug + * @since 1.16.7 + */ + private String dingTalkCorpId; + + /** + * 钉钉平台参数:true表示专属帐号登录,展示组织代码输入页。 + * + * @see https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug + * @since 1.16.7 + */ + private boolean dingTalkExclusiveLogin; + + /** + * 钉钉平台参数:开启了专属帐号功能的组织corpId。 + * + * scope包含corpid时该参数存在意义。传入的corpId需要是当前用户所在的组织。 + * + * @see https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug + * @since 1.16.7 + */ + private String dingTalkExclusiveCorpId; } diff --git a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java index 4b583a5..97bca55 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java +++ b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java @@ -112,6 +112,30 @@ public enum AuthDefaultSource implements AuthSource { return AuthDingTalkRequest.class; } }, + /** + * 新版钉钉扫码登录 + */ + DINGTALK_V2 { + @Override + public String authorize() { + return "https://login.dingtalk.com/oauth2/challenge.htm"; + } + + @Override + public String accessToken() { + return "https://api.dingtalk.com/v1.0/oauth2/userAccessToken"; + } + + @Override + public String userInfo() { + return "https://api.dingtalk.com/v1.0/contact/users/me"; + } + + @Override + public Class getTargetClass() { + return AuthDingTalkV2Request.class; + } + }, /** * 钉钉账号登录 */ diff --git a/src/main/java/me/zhyd/oauth/enums/scope/AuthDingTalkScope.java b/src/main/java/me/zhyd/oauth/enums/scope/AuthDingTalkScope.java new file mode 100644 index 0000000..5971078 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/enums/scope/AuthDingTalkScope.java @@ -0,0 +1,33 @@ +package me.zhyd.oauth.enums.scope; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 钉钉平台 OAuth 授权范围 + * + * https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.16.7 + */ +@Getter +@AllArgsConstructor +public enum AuthDingTalkScope implements AuthScope { + + /** + * 无需申请 默认开启 + */ + openid("openid", "授权后可获得用户userid", true), + /** + * 无需申请 默认开启 + */ + corpid("corpid", "授权后可获得登录过程中用户选择的组织id", false) + ; + + private final String scope; + private final String description; + private final boolean isDefault; + +} diff --git a/src/main/java/me/zhyd/oauth/model/AuthToken.java b/src/main/java/me/zhyd/oauth/model/AuthToken.java index 321da54..706f95c 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthToken.java +++ b/src/main/java/me/zhyd/oauth/model/AuthToken.java @@ -66,4 +66,11 @@ public class AuthToken implements Serializable { * Apple附带属性 */ private String username; + + /** + * 新版钉钉附带属性 + * + * @since 1.16.7 + */ + private String corpId; } diff --git a/src/main/java/me/zhyd/oauth/request/AuthDingTalkV2Request.java b/src/main/java/me/zhyd/oauth/request/AuthDingTalkV2Request.java new file mode 100644 index 0000000..e4a4f99 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthDingTalkV2Request.java @@ -0,0 +1,108 @@ +package me.zhyd.oauth.request; + +import com.alibaba.fastjson.JSONObject; +import com.xkcoding.http.support.HttpHeader; +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthDefaultSource; +import me.zhyd.oauth.enums.scope.AuthDingTalkScope; +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.AuthScopeUtils; +import me.zhyd.oauth.utils.HttpUtils; +import me.zhyd.oauth.utils.UrlBuilder; + +import java.util.HashMap; +import java.util.Map; + +/** + * 新版钉钉二维码登录 + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @since 1.16.7 + */ +public class AuthDingTalkV2Request extends AuthDefaultRequest { + + public AuthDingTalkV2Request(AuthConfig config) { + super(config, AuthDefaultSource.DINGTALK_V2); + } + + public AuthDingTalkV2Request(AuthConfig config, AuthStateCache authStateCache) { + super(config, AuthDefaultSource.DINGTALK_V2, authStateCache); + } + + @Override + public String authorize(String state) { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("scope", this.getScopes(",", true, AuthScopeUtils.getDefaultScopes(AuthDingTalkScope.values()))) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("prompt", "consent") + .queryParam("org_type", config.getDingTalkOrgType()) + .queryParam("corpId", config.getDingTalkCorpId()) + .queryParam("exclusiveLogin", config.isDingTalkExclusiveLogin()) + .queryParam("exclusiveCorpId", config.getDingTalkExclusiveCorpId()) + .queryParam("state", getRealState(state)) + .build(); + } + + @Override + public AuthToken getAccessToken(AuthCallback authCallback) { + Map params = new HashMap<>(); + params.put("grantType", "authorization_code"); + params.put("clientId", config.getClientId()); + params.put("clientSecret", config.getClientSecret()); + params.put("code", authCallback.getCode()); + String response = new HttpUtils(config.getHttpConfig()).post(this.source.accessToken(), JSONObject.toJSONString(params)).getBody(); + JSONObject accessTokenObject = JSONObject.parseObject(response); + if (!accessTokenObject.containsKey("accessToken")) { + throw new AuthException(JSONObject.toJSONString(response), source); + } + return AuthToken.builder() + .accessToken(accessTokenObject.getString("accessToken")) + .refreshToken(accessTokenObject.getString("refreshToken")) + .expireIn(accessTokenObject.getIntValue("expireIn")) + .corpId(accessTokenObject.getString("corpId")) + .build(); + } + + @Override + public AuthUser getUserInfo(AuthToken authToken) { + HttpHeader header = new HttpHeader(); + header.add("x-acs-dingtalk-access-token", authToken.getAccessToken()); + + String response = new HttpUtils(config.getHttpConfig()).get(this.source.userInfo(), null, header, false).getBody(); + JSONObject object = JSONObject.parseObject(response); + + authToken.setOpenId(object.getString("openId")); + authToken.setUnionId(object.getString("unionId")); + return AuthUser.builder() + .rawUserInfo(object) + .uuid(object.getString("unionId")) + .username(object.getString("nick")) + .nickname(object.getString("nick")) + .avatar(object.getString("avatarUrl")) + .snapshotUser(object.getBooleanValue("visitor")) + .token(authToken) + .source(source.toString()) + .build(); + } + + /** + * 返回获取accessToken的url + * + * @param code 授权码 + * @return 返回获取accessToken的url + */ + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("code", code) + .queryParam("clientId", config.getClientId()) + .queryParam("clientSecret", config.getClientSecret()) + .queryParam("grantType", "authorization_code") + .build(); + } +}