feat(auth): add DingTalk OAuth2 login support - #467
Conversation
a1dd34c to
f7370c8
Compare
|
I have completed the CLA signing. Please re-run the CLA check, thanks. |
|
感谢 @konglong87 的贡献!钉钉登录集成对企业用户很有价值。整体实现思路清晰,正确处理了钉钉的非标准 OAuth2 流程(JSON token 交换 + 自定义 header)。 我们进行了多视角代码审查,发现了一些需要修复的问题。其中有 2 个阻断性问题必须在合并前解决: 🚨 必须修复1. 身份标识选择错误(Critical)问题: 修复:使用 // DingTalkClaimsExtractor.java
String unionId = (String) attrs.get("unionId");
String openId = (String) attrs.get("openId");
if (unionId == null || unionId.isEmpty()) {
throw new OAuth2AuthenticationException(
new OAuth2Error("missing_union_id",
"DingTalk response missing required unionId field", null));
}
return new OAuthClaims(
"dingtalk",
unionId, // 使用 unionId 而非 openId
syntheticEmail,
true,
nick,
attrs
);2. Scope 配置错误(Critical)问题: 修复: # application.yml
dingtalk:
scope:
- openid # 或 "openid corpid" 如果需要企业信息同步修改
|
|
already fix,please check it.
|
|
感谢 review!: 阻断性问题:
强烈建议项: 其他建议: |
|
#583 正在等 DingTalk 登录能力,这个 PR 值得继续推进。目前和 main 冲突,请 rebase,解决后我们优先看。 |
a8951b7 to
37f159e
Compare
已 rebase 到最新 main,冲突已解决,请重新审查。谢谢! |
XiaoSeS
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
基于当前 HEAD 37f159ef、全部评论及关联需求 #583 重新 Review。此前指出的 unionId、超时、token 响应空值和 raw response 问题已有改进,但当前仍不能进入 big-main:
-
expireIn没有写入 AccessToken,token 实际约 1 秒后过期。DingTalkTokenResponseClient.java:102-110只把expireIn放进additionalParameters,没有调用 Builder 的.expiresIn(...)。项目当前 Spring Security 6.2.2 在未设置时把expiresAt设为issuedAt + 1s,会造成 user-info 请求竞态和授权客户端立即判定过期。请把合法的expireIn写入 Builder,并在测试中断言getExpiresAt()。 -
Provider 并非默认关闭。
application.yml使用 placeholder 注册 DingTalk,AuthMethodCatalog会无条件枚举它,因此未配置凭证时登录页也会出现一个必然失败的钉钉入口;AuthControllerTest还把这个行为固定成 4 个 Provider。这与 #583 的“disabled unless explicitly configured”相反。请增加明确的启用条件/独立 profile,并覆盖启用与未启用两种目录行为。 -
Release 配置实际传不到容器。
.env.release.example增加了OAUTH2_DINGTALK_*,但compose.release.yml的 Server environment 没有透传这些变量,所以按文档配置仍会使用 placeholder。请补齐 Docker/Compose 与 Kubernetes 的运行时配置链路和验证。 -
身份映射不符合 #583。
DingTalkClaimsExtractor强制要求 unionId,未实现需求中的unionId -> openId -> userId稳定回退;同时合成unionId@dingtalk.local并标记emailVerified=true。合成地址不是钉钉验证过的邮箱,也会错误参与 EMAIL_DOMAIN 准入和覆盖用户资料。无真实邮箱时应传null/false,identity 仍只依赖provider + subject。 -
Scope、文档和测试互相矛盾。 生产配置及中英文认证文档写
corpid,.env.release.example和所有新单测却使用openid;openid还会让 Spring Security 走 OIDC 分支并期待id_token,这些直接调用类的单测没有覆盖真实 FilterChain 路由。请确定唯一可工作的授权参数,引用钉钉官方契约,并增加 mock token/user-info 端点的完整 OAuth callback 集成测试,同时证明 GitHub/GitLab/OIDC 没有回归。 -
错误与变更范围仍需收口。 user-info HTTP 失败没有包装为
OAuth2AuthenticationException;application-local.yml无关地移除了原日志级别并把整个 auth 包改成 DEBUG;SkillStorageDeletionCompensationJpaRepository的 public 修改与钉钉无关;新 Java/测试文件普遍缺 EOF 换行。请移除无关改动,并按项目规则不要直接修改生成的document/docs/,改权威文档源。
现有 Server/Web/Docs CI 通过;E2E 是 Scanner 构建缺 libgcc_s.so.1,在认证链路启动前失败,因此没有证明钉钉功能。DCO 仍失败、CLA 仍 pending。上述阻塞修复后再重新 Review;本轮维护者不会向近期外部贡献分支追加 commit。
993a4a4 to
b1ea237
Compare
感谢 Review。已基于最新 本轮已处理所有阻塞项:
当前 CLA、DCO 均已通过,PR 已无合并冲突。 麻烦基于最新 HEAD |
DingTalk (钉钉) uses a non-standard OAuth2 flow that requires: - JSON body for token exchange (instead of form-urlencoded) - Custom header (x-acs-dingtalk-access-token) for user info requests This PR integrates DingTalk by leveraging the existing OAuthClaimsExtractor strategy pattern, adding three provider-specific components: - DingTalkClaimsExtractor: maps DingTalk user fields to normalized OAuthClaims - DingTalkTokenResponseClient: handles DingTalk's JSON token exchange - DingTalkOAuth2UserService: fetches user info via DingTalk's custom header SecurityConfig uses delegating wrappers to route DingTalk requests to these custom components while preserving standard behavior for all other providers (GitHub, GitLab, OIDC). No changes needed to OAuthLoginFlowService, IdentityBindingService, AuthMethodCatalog, or frontend LoginButton — all are provider-agnostic. Signed-off-by: konglong87 <38234954+konglong87@users.noreply.github.com>
…ess policy DingTalkOAuth2UserService was directly calling IdentityBindingService.bindOrCreate(), bypassing access policy evaluation. Refactored to delegate to OAuthLoginFlowService.authenticate() for consistent policy + binding, matching the pattern used by CustomOAuth2UserService. Also aligned the returned DefaultOAuth2User structure (providerLogin attribute key, authorities from platformRoles) with CustomOAuth2UserService so OAuth2LoginSuccessHandler works uniformly across all providers. Signed-off-by: konglong87 <38234954+konglong87@users.noreply.github.com>
…port Signed-off-by: konglong87 <38234954+konglong87@users.noreply.github.com>
…ring Data JPA proxy Package-private JPA repository interfaces cannot be proxied by Spring Data's JpaRepositoryFactory when using Spring Boot devtools or certain class loader configurations, causing UnsatisfiedDependencyException at startup. Signed-off-by: konglong87 <38234954+konglong87@users.noreply.github.com>
1. 身份标识: openId → unionId (跨应用唯一) - DingTalkClaimsExtractor: subject使用unionId, 新增null校验 - DingTalkOAuth2UserService: nameAttribute改为unionId - application.yml: user-name-attribute改为unionId 2. Scope配置: dingtalk → openid (钉钉OAuth2正确scope) - application.yml: scope改为openid - .env.release.example: 新增scope说明注释 3. RestTemplate超时配置 (5s connect + 10s read) - DingTalkTokenResponseClient: 防止无限阻塞 - DingTalkOAuth2UserService: 同步配置 4. NPE风险修复 - DingTalkTokenResponseClient: JsonNode null检查 5. 敏感信息泄露修复 - DingTalkTokenResponseClient: 移除raw_response, 只保留expireIn 6. 硬编码URL修复 - DingTalkOAuth2UserService: userInfoUri从ClientRegistration配置读取 7. 单元测试覆盖 (12个测试全部通过) - DingTalkClaimsExtractorTest: 4个 - DingTalkTokenResponseClientTest: 6个 - DingTalkOAuth2UserServiceTest: 2个 Signed-off-by: konglong87 <38234954+konglong87@users.noreply.github.com>
- Change SecurityConfig to @configuration(proxyBeanMethods=false) to avoid CGLIB proxy issues with constructor-injected beans - Add @Autowired to DingTalkOAuth2UserService public constructor so Spring resolves the correct constructor when multiple constructors exist - Change DingTalk scope from openid to corpid: DingTalk does not return id_token in its token response, so openid scope causes Spring Security to fail with invalid_id_token error. corpid scope works correctly with DingTalk's authorization endpoint - Add error logging to OAuth2LoginFailureHandler for easier debugging - Add DingTalk client-id/client-secret env vars to application-local.yml Signed-off-by: konglong87 <38234954+konglong87@users.noreply.github.com>
Signed-off-by: konglong87 <38234954+konglong87@users.noreply.github.com>
Signed-off-by: konglong87 <38234954+konglong87@users.noreply.github.com>
b1ea237 to
0b21fe2
Compare
|
维护者复核当前 HEAD
当前仍不直接合并:
|
|
补充一个当前 HEAD 的范围/仓库规则问题:
这两项不改变前面 OAuth 代码修复的判断,但在完整 CI 与 big-main 集成前需要收口。 |
|
在最新 当前阻塞是编译级兼容性问题:
因此本轮 |
感谢您的复核和集成验证! 我注意到后续 PR #677 已完成新版统一身份核心下的 DingTalk OAuth2 适配,并已合并到 为避免在 #467 中重复实现相同适配器或产生新的合并冲突,想确认两点:
在您确认前我先不继续修改或强推 #467,避免重复代码。 |
Add DingTalk (钉钉) OAuth2 login support to SkillHub.
DingTalk uses a non-standard OAuth2 flow:
This PR leverages the existing OAuthClaimsExtractor strategy pattern, adding three provider-specific components:
┌─────────────────────────────┬────────────────────────────────────────────────────────────────────────┐
│ New file │ Purpose │
├─────────────────────────────┼────────────────────────────────────────────────────────────────────────┤
│ DingTalkClaimsExtractor │ Maps DingTalk fields (openId, nick, unionId) to normalized OAuthClaims │
├─────────────────────────────┼────────────────────────────────────────────────────────────────────────┤
│ DingTalkTokenResponseClient │ Handles DingTalk JSON token exchange │
├─────────────────────────────┼────────────────────────────────────────────────────────────────────────┤
│ DingTalkOAuth2UserService │ Fetches user info via DingTalk custom header │
└─────────────────────────────┴────────────────────────────────────────────────────────────────────────┘
SecurityConfig uses delegating wrappers to route registrationId=dingtalk to these custom components, preserving standard behavior for GitHub, GitLab, and OIDC.
Files changed (7 total)
New (4):
Modified (3):
Zero-change components
No changes needed to:
Configuration
Set these environment variables to enable DingTalk login:
OAUTH2_DINGTALK_CLIENT_ID=
OAUTH2_DINGTALK_CLIENT_SECRET=
Register your app at https://open-dev.dingtalk.com/ and request the Contact.User.Read scope.
Test plan