Commit ea5fd5d9034685196f68f7da875308cdc329d99c

Authored by YunaiV
1 parent 8aed362e

feat(system): 升级钉钉 V2 社交登录并校验用户标识

yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java
@@ -25,9 +25,11 @@ public enum SocialTypeEnum implements ArrayValuable<Integer> { @@ -25,9 +25,11 @@ public enum SocialTypeEnum implements ArrayValuable<Integer> {
25 /** 25 /**
26 * 钉钉 26 * 钉钉
27 * 27 *
28 - * @see <a href="https://developers.dingtalk.com/document/app/obtain-identity-credentials">接入文档</a> 28 + * 使用新版登录,对应 JustAuth 的 DINGTALK_V2,相关反馈:https://t.zsxq.com/QY7xI
  29 + *
  30 + * @see <a href="https://open.dingtalk.com/document/app/obtain-identity-credentials">接入文档</a>
29 */ 31 */
30 - DINGTALK(20, "DINGTALK"), 32 + DINGTALK(20, "DINGTALK_V2"),
31 33
32 /** 34 /**
33 * 企业微信 35 * 企业微信
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
@@ -16,6 +16,7 @@ import cn.hutool.core.thread.ThreadUtil; @@ -16,6 +16,7 @@ import cn.hutool.core.thread.ThreadUtil;
16 import cn.hutool.core.util.DesensitizedUtil; 16 import cn.hutool.core.util.DesensitizedUtil;
17 import cn.hutool.core.util.ObjUtil; 17 import cn.hutool.core.util.ObjUtil;
18 import cn.hutool.core.util.ReflectUtil; 18 import cn.hutool.core.util.ReflectUtil;
  19 +import cn.hutool.core.util.StrUtil;
19 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 20 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
20 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 21 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
21 import cn.iocoder.yudao.framework.common.pojo.PageResult; 22 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -187,7 +188,12 @@ public class SocialClientServiceImpl implements SocialClientService { @@ -187,7 +188,12 @@ public class SocialClientServiceImpl implements SocialClientService {
187 if (!authResponse.ok()) { 188 if (!authResponse.ok()) {
188 throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg()); 189 throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg());
189 } 190 }
190 - return (AuthUser) authResponse.getData(); 191 + // 部分平台获取用户信息失败时,仍然返回成功,但没有用户标识。例如说,钉钉新版登录缺少权限时,接口返回 403,但 JustAuth 不校验
  192 + AuthUser authUser = (AuthUser) authResponse.getData();
  193 + if (authUser == null || StrUtil.isBlank(authUser.getUuid())) {
  194 + throw exception(SOCIAL_USER_AUTH_FAILURE, "未获取到用户标识");
  195 + }
  196 + return authUser;
191 } 197 }
192 198
193 /** 199 /**
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java
@@ -146,6 +146,48 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest { @@ -146,6 +146,48 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
146 } 146 }
147 147
148 @Test 148 @Test
  149 + public void testAuthSocialUser_uuidNull() {
  150 + // 准备参数
  151 + Integer socialType = SocialTypeEnum.DINGTALK.getType();
  152 + Integer userType = randomPojo(UserTypeEnum.class).getValue();
  153 + String code = randomString();
  154 + String state = randomString();
  155 + // mock 方法(AuthRequest)
  156 + AuthRequest authRequest = mock(AuthRequest.class);
  157 + when(authRequestFactory.get(eq("DINGTALK_V2"))).thenReturn(authRequest);
  158 + // mock 方法(AuthResponse):获取用户信息失败时,JustAuth 仍然返回成功,但 uuid 为空
  159 + AuthUser authUser = randomPojo(AuthUser.class, o -> o.setUuid(null));
  160 + AuthResponse<AuthUser> authResponse = new AuthResponse<>(2000, null, authUser);
  161 + when(authRequest.login(any())).thenReturn(authResponse);
  162 +
  163 + // 调用并断言
  164 + assertServiceException(
  165 + () -> socialClientService.getAuthUser(socialType, userType, code, state),
  166 + SOCIAL_USER_AUTH_FAILURE, "未获取到用户标识");
  167 + }
  168 +
  169 + @Test
  170 + public void testAuthSocialUser_uuidBlank() {
  171 + // 准备参数
  172 + Integer socialType = SocialTypeEnum.DINGTALK.getType();
  173 + Integer userType = randomPojo(UserTypeEnum.class).getValue();
  174 + String code = randomString();
  175 + String state = randomString();
  176 + // mock 方法(AuthRequest)
  177 + AuthRequest authRequest = mock(AuthRequest.class);
  178 + when(authRequestFactory.get(eq("DINGTALK_V2"))).thenReturn(authRequest);
  179 + // mock 方法(AuthResponse)
  180 + AuthUser authUser = randomPojo(AuthUser.class, o -> o.setUuid(" "));
  181 + AuthResponse<AuthUser> authResponse = new AuthResponse<>(2000, null, authUser);
  182 + when(authRequest.login(any())).thenReturn(authResponse);
  183 +
  184 + // 调用并断言
  185 + assertServiceException(
  186 + () -> socialClientService.getAuthUser(socialType, userType, code, state),
  187 + SOCIAL_USER_AUTH_FAILURE, "未获取到用户标识");
  188 + }
  189 +
  190 + @Test
149 public void testBuildAuthRequest_clientNull() { 191 public void testBuildAuthRequest_clientNull() {
150 // 准备参数 192 // 准备参数
151 Integer socialType = SocialTypeEnum.WECHAT_MP.getType(); 193 Integer socialType = SocialTypeEnum.WECHAT_MP.getType();
yudao-server/src/main/resources/application-dev.yaml
@@ -181,7 +181,7 @@ yudao: @@ -181,7 +181,7 @@ yudao:
181 justauth: 181 justauth:
182 enabled: true 182 enabled: true
183 type: 183 type:
184 - DINGTALK: # 钉钉 184 + DINGTALK_V2: # 钉钉(新版登录)
185 client-id: dingvrnreaje3yqvzhxg 185 client-id: dingvrnreaje3yqvzhxg
186 client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI 186 client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
187 ignore-check-redirect-uri: true 187 ignore-check-redirect-uri: true
yudao-server/src/main/resources/application-local.yaml
@@ -254,7 +254,7 @@ yudao: @@ -254,7 +254,7 @@ yudao:
254 justauth: 254 justauth:
255 enabled: true 255 enabled: true
256 type: 256 type:
257 - DINGTALK: # 钉钉 257 + DINGTALK_V2: # 钉钉(新版登录)
258 client-id: dingvrnreaje3yqvzhxg 258 client-id: dingvrnreaje3yqvzhxg
259 client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI 259 client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
260 ignore-check-redirect-uri: true 260 ignore-check-redirect-uri: true