Skip to content

feat(auth): add DingTalk OAuth2 login support - #467

Open
konglong87 wants to merge 8 commits into
iflytek:mainfrom
konglong87:feature/dingtalk-oauth2
Open

feat(auth): add DingTalk OAuth2 login support#467
konglong87 wants to merge 8 commits into
iflytek:mainfrom
konglong87:feature/dingtalk-oauth2

Conversation

@konglong87

Copy link
Copy Markdown

Add DingTalk (钉钉) OAuth2 login support to SkillHub.

DingTalk uses a non-standard OAuth2 flow:

  • Token exchange: JSON body instead of form-urlencoded
  • User info: Custom header x-acs-dingtalk-access-token instead of Authorization: Bearer

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):

  • DingTalkClaimsExtractor.java
  • DingTalkTokenResponseClient.java
  • DingTalkOAuth2UserService.java
  • dingtalk-logo.svg

Modified (3):

  • SecurityConfig.java — added delegating token/user service routing
  • application.yml — added DingTalk OAuth2 registration (env vars, no hardcoded secrets)
  • .env.release.example — added OAUTH2_DINGTALK_CLIENT_ID/SECRET env vars

Zero-change components

No changes needed to:

  • OAuthLoginFlowService — auto-discovers all OAuthClaimsExtractor beans
  • IdentityBindingService — handles any providerCode generically
  • AuthMethodCatalog — dynamically lists all OAuth registrations
  • LoginButton (frontend) — dynamically renders all OAUTH_REDIRECT methods with provider logos

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

  • Verify DingTalk OAuth2 login flow works end-to-end
  • Verify existing GitHub/GitLab/OIDC login still works
  • Verify DingTalk login button appears on login page
  • Verify user provisioning via IdentityBindingService.bindOrCreate()
  • Verify access policy evaluation for DingTalk provider

@CLAassistant

CLAassistant commented May 28, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@konglong87
konglong87 force-pushed the feature/dingtalk-oauth2 branch from a1dd34c to f7370c8 Compare May 28, 2026 07:04
@konglong87

Copy link
Copy Markdown
Author

I have completed the CLA signing. Please re-run the CLA check, thanks.

@dongmucat

Copy link
Copy Markdown
Collaborator

感谢 @konglong87 的贡献!钉钉登录集成对企业用户很有价值。整体实现思路清晰,正确处理了钉钉的非标准 OAuth2 流程(JSON token 交换 + 自定义 header)。

我们进行了多视角代码审查,发现了一些需要修复的问题。其中有 2 个阻断性问题必须在合并前解决


🚨 必须修复

1. 身份标识选择错误(Critical)

问题DingTalkClaimsExtractor 使用 openId 作为 subject,但 openId 仅在单个钉钉应用内唯一。如果用户通过不同钉钉应用登录,会被识别为不同账号。

修复:使用 unionId(在同一开发者的所有应用中唯一):

// 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
);

参考:钉钉文档 - unionId 说明


2. Scope 配置错误(Critical)

问题application.ymlscope: dingtalk 不是有效值,钉钉支持的 scope 是 openidopenid corpid

修复

# application.yml
dingtalk:
  scope:
    - openid  # 或 "openid corpid" 如果需要企业信息

同步修改 .env.release.example 中的注释说明。


⚠️ 强烈建议修复

3. RestTemplate 缺少超时配置

两处 new RestTemplate() 未配置超时,钉钉 API 无响应时会导致线程阻塞。建议参考项目中 CasTicketValidator 的做法:

private RestTemplate buildRestTemplate() {
    var factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(Duration.ofSeconds(5));
    factory.setReadTimeout(Duration.ofSeconds(10));
    return new RestTemplate(factory);
}

4. NPE 风险

DingTalkTokenResponseClient.java:65 行:

// 当前
String accessToken = json.get("accessToken").asText();  // get() 可能返回 null

// 建议
JsonNode accessTokenNode = json.get("accessToken");
if (accessTokenNode == null || accessTokenNode.isNull()) {
    throw new OAuth2AuthenticationException(
        new OAuth2Error("token_response_missing_field", ...));
}
String accessToken = accessTokenNode.asText();

5. 敏感信息泄露

additionalParameters 中的 raw_response 包含完整 token 响应(可能含 refreshToken),建议移除或只保留必要字段(如 expireIn)。


📝 其他建议(可后续 PR)

  • 测试覆盖:3 个新类缺少单元测试,建议补充(可参考 CasTicketValidatorTest 的 mock 模式)
  • 硬编码 URLDingTalkOAuth2UserService 中的 user-info URL 应从 userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri() 读取
  • HTTP 错误处理DingTalkOAuth2UserService.loadUser() 中的 restTemplate.exchange() 应捕获异常并包装为 OAuth2AuthenticationException
  • 不相关变更SkillStorageDeletionCompensationJpaRepository 的可见性修改应独立提交或在 commit message 中说明原因

📚 部署文档补充

建议在文档中说明钉钉应用配置要求:

  1. 需在钉钉开放平台开启 Contact.User.Read 权限
  2. 需发布应用版本才能激活凭证
  3. 回调地址配置为 {baseUrl}/login/oauth2/code/dingtalk

修复前 2 个阻断性问题后我们会立即 review。如有疑问欢迎随时讨论!🙌

@konglong87

Copy link
Copy Markdown
Author

already fix,please check it.

感谢 @konglong87 的贡献!钉钉登录集成对企业用户很有价值。整体实现思路清晰,正确处理了钉钉的非标准 OAuth2 流程(JSON token 交换 + 自定义 header)。

我们进行了多视角代码审查,发现了一些需要修复的问题。其中有 2 个阻断性问题必须在合并前解决

🚨 必须修复

1. 身份标识选择错误(Critical)

问题DingTalkClaimsExtractor 使用 openId 作为 subject,但 openId 仅在单个钉钉应用内唯一。如果用户通过不同钉钉应用登录,会被识别为不同账号。

修复:使用 unionId(在同一开发者的所有应用中唯一):

// 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
);

参考:钉钉文档 - unionId 说明

2. Scope 配置错误(Critical)

问题application.ymlscope: dingtalk 不是有效值,钉钉支持的 scope 是 openidopenid corpid

修复

# application.yml
dingtalk:
  scope:
    - openid  # 或 "openid corpid" 如果需要企业信息

同步修改 .env.release.example 中的注释说明。

⚠️ 强烈建议修复

3. RestTemplate 缺少超时配置

两处 new RestTemplate() 未配置超时,钉钉 API 无响应时会导致线程阻塞。建议参考项目中 CasTicketValidator 的做法:

private RestTemplate buildRestTemplate() {
    var factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(Duration.ofSeconds(5));
    factory.setReadTimeout(Duration.ofSeconds(10));
    return new RestTemplate(factory);
}

4. NPE 风险

DingTalkTokenResponseClient.java:65 行:

// 当前
String accessToken = json.get("accessToken").asText();  // get() 可能返回 null

// 建议
JsonNode accessTokenNode = json.get("accessToken");
if (accessTokenNode == null || accessTokenNode.isNull()) {
    throw new OAuth2AuthenticationException(
        new OAuth2Error("token_response_missing_field", ...));
}
String accessToken = accessTokenNode.asText();

5. 敏感信息泄露

additionalParameters 中的 raw_response 包含完整 token 响应(可能含 refreshToken),建议移除或只保留必要字段(如 expireIn)。

📝 其他建议(可后续 PR)

  • 测试覆盖:3 个新类缺少单元测试,建议补充(可参考 CasTicketValidatorTest 的 mock 模式)
  • 硬编码 URLDingTalkOAuth2UserService 中的 user-info URL 应从 userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri() 读取
  • HTTP 错误处理DingTalkOAuth2UserService.loadUser() 中的 restTemplate.exchange() 应捕获异常并包装为 OAuth2AuthenticationException
  • 不相关变更SkillStorageDeletionCompensationJpaRepository 的可见性修改应独立提交或在 commit message 中说明原因

📚 部署文档补充

建议在文档中说明钉钉应用配置要求:

  1. 需在钉钉开放平台开启 Contact.User.Read 权限
  2. 需发布应用版本才能激活凭证
  3. 回调地址配置为 {baseUrl}/login/oauth2/code/dingtalk

修复前 2 个阻断性问题后我们会立即 review。如有疑问欢迎随时讨论!🙌

@konglong87

Copy link
Copy Markdown
Author

感谢 review!:

阻断性问题:

  1. ✅ 已修复 — DingTalkClaimsExtractor 使用 unionId 作为 subject,含 null 校验
  2. ⚠️ 需确认 — 当前 scope 为 corpid。我们最初使用 openid,但钉钉授权端点不接受 openid 作为 scope 参数,导致回调 401。改为 corpid 后实测通过,可正常获取 unionId。

强烈建议项:
3. ✅ 已修复 — 两处 RestTemplate 均配置 5s/10s 超时
4. ✅ 已修复 — accessToken 增加 null/isNull/empty 校验
5. ✅ 已修复 — additionalParameters 只保留 expireIn

其他建议:
6. ✅ 已修复 — userInfoUri 从 providerDetails 读取
7. ✅ 已补充 — 部署文档(authentication.md + configuration.md,中英文)

@FenjuFu

FenjuFu commented Jul 17, 2026

Copy link
Copy Markdown
Member

#583 正在等 DingTalk 登录能力,这个 PR 值得继续推进。目前和 main 冲突,请 rebase,解决后我们优先看。

@konglong87
konglong87 force-pushed the feature/dingtalk-oauth2 branch from a8951b7 to 37f159e Compare July 20, 2026 11:13
@konglong87

Copy link
Copy Markdown
Author

#583 正在等 DingTalk 登录能力,这个 PR 值得继续推进。目前和 main 冲突,请 rebase,解决后我们优先看。

已 rebase 到最新 main,冲突已解决,请重新审查。谢谢!

@XiaoSeS XiaoSeS left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

基于当前 HEAD 37f159ef、全部评论及关联需求 #583 重新 Review。此前指出的 unionId、超时、token 响应空值和 raw response 问题已有改进,但当前仍不能进入 big-main

  1. 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()

  2. Provider 并非默认关闭。 application.yml 使用 placeholder 注册 DingTalk,AuthMethodCatalog 会无条件枚举它,因此未配置凭证时登录页也会出现一个必然失败的钉钉入口;AuthControllerTest 还把这个行为固定成 4 个 Provider。这与 #583 的“disabled unless explicitly configured”相反。请增加明确的启用条件/独立 profile,并覆盖启用与未启用两种目录行为。

  3. Release 配置实际传不到容器。 .env.release.example 增加了 OAUTH2_DINGTALK_*,但 compose.release.yml 的 Server environment 没有透传这些变量,所以按文档配置仍会使用 placeholder。请补齐 Docker/Compose 与 Kubernetes 的运行时配置链路和验证。

  4. 身份映射不符合 #583 DingTalkClaimsExtractor 强制要求 unionId,未实现需求中的 unionId -> openId -> userId 稳定回退;同时合成 unionId@dingtalk.local 并标记 emailVerified=true。合成地址不是钉钉验证过的邮箱,也会错误参与 EMAIL_DOMAIN 准入和覆盖用户资料。无真实邮箱时应传 null/false,identity 仍只依赖 provider + subject

  5. Scope、文档和测试互相矛盾。 生产配置及中英文认证文档写 corpid.env.release.example 和所有新单测却使用 openidopenid 还会让 Spring Security 走 OIDC 分支并期待 id_token,这些直接调用类的单测没有覆盖真实 FilterChain 路由。请确定唯一可工作的授权参数,引用钉钉官方契约,并增加 mock token/user-info 端点的完整 OAuth callback 集成测试,同时证明 GitHub/GitLab/OIDC 没有回归。

  6. 错误与变更范围仍需收口。 user-info HTTP 失败没有包装为 OAuth2AuthenticationExceptionapplication-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。

@konglong87
konglong87 force-pushed the feature/dingtalk-oauth2 branch 3 times, most recently from 993a4a4 to b1ea237 Compare July 29, 2026 04:51
@konglong87

Copy link
Copy Markdown
Author

This was generated by AI during triage.

基于当前 HEAD 37f159ef、全部评论及关联需求 #583 重新 Review。此前指出的 unionId、超时、token 响应空值和 raw response 问题已有改进,但当前仍不能进入 big-main

  1. 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()
  2. Provider 并非默认关闭。 application.yml 使用 placeholder 注册 DingTalk,AuthMethodCatalog 会无条件枚举它,因此未配置凭证时登录页也会出现一个必然失败的钉钉入口;AuthControllerTest 还把这个行为固定成 4 个 Provider。这与 Add DingTalk OAuth login provider #583 的“disabled unless explicitly configured”相反。请增加明确的启用条件/独立 profile,并覆盖启用与未启用两种目录行为。
  3. Release 配置实际传不到容器。 .env.release.example 增加了 OAUTH2_DINGTALK_*,但 compose.release.yml 的 Server environment 没有透传这些变量,所以按文档配置仍会使用 placeholder。请补齐 Docker/Compose 与 Kubernetes 的运行时配置链路和验证。
  4. 身份映射不符合 Add DingTalk OAuth login provider #583 DingTalkClaimsExtractor 强制要求 unionId,未实现需求中的 unionId -> openId -> userId 稳定回退;同时合成 unionId@dingtalk.local 并标记 emailVerified=true。合成地址不是钉钉验证过的邮箱,也会错误参与 EMAIL_DOMAIN 准入和覆盖用户资料。无真实邮箱时应传 null/false,identity 仍只依赖 provider + subject
  5. Scope、文档和测试互相矛盾。 生产配置及中英文认证文档写 corpid.env.release.example 和所有新单测却使用 openidopenid 还会让 Spring Security 走 OIDC 分支并期待 id_token,这些直接调用类的单测没有覆盖真实 FilterChain 路由。请确定唯一可工作的授权参数,引用钉钉官方契约,并增加 mock token/user-info 端点的完整 OAuth callback 集成测试,同时证明 GitHub/GitLab/OIDC 没有回归。
  6. 错误与变更范围仍需收口。 user-info HTTP 失败没有包装为 OAuth2AuthenticationExceptionapplication-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。

感谢 Review。已基于最新 main 完成修复和 rebase,当前 HEAD 为 b1ea237f

本轮已处理所有阻塞项:

  • 将合法的 expireIn 写入 OAuth2AccessTokenResponse.Builder.expiresIn(...),并补充 expiresAt 断言。
  • DingTalk Provider 改为通过独立 dingtalk profile 显式启用,默认认证目录不再显示钉钉;测试覆盖启用和未启用场景。
  • 补齐了 Compose 和 Kubernetes 的 DingTalk 配置透传及配置验证。
  • 身份映射 subject 按 unionId -> openId -> userId 稳定回退;无真实邮箱时传 nullemailVerified=false,身份绑定仍仅依赖 provider + subject。
  • 授权参数统一为钉钉官方新版 OAuth2 契约的 openid。授权 URL 保留 scope=openid,但适配器会从 Spring Security 内部 scopes 和 nonce 中移除它,避免误走 OIDC/id_token 分支。
  • 新增 mock token/user-info 端点的完整 OAuth callback 集成测试,并验证 GitHub、GitLab 和标准 OIDC 路由未回归。
  • token/user-info HTTP 异常统一包装为 OAuth2AuthenticationException
  • 移除无关代码、日志配置和生成文档目录的净改动,并检查新增文件 EOF 换行。

当前 CLA、DCO 均已通过,PR 已无合并冲突。
新的 GitHub Actions workflow 仍处于 action_required,jobs 尚未启动,麻烦批准运行外部分支 workflow。

麻烦基于最新 HEAD b1ea237f 重新 Review,并批准运行 CI,谢谢了。

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>
@konglong87
konglong87 force-pushed the feature/dingtalk-oauth2 branch from b1ea237 to 0b21fe2 Compare July 30, 2026 07:28
@XiaoSeS

XiaoSeS commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

维护者复核当前 HEAD 0b21fe2f34c6901abf5bc017e37f1d432e66fe33:此前提出的代码阻塞项在当前提交中已看到对应处理:

  • DingTalk subject 按 unionId -> openId -> userId 回退;不再把合成邮箱当作已验证邮箱。
  • token response 校验 accessToken / expireIn,并把 expireIn 写入 expiresIn(...);additional parameters 不保留原始 token 响应。
  • token 与 user-info HTTP 客户端有连接/读取超时和安全异常映射。
  • DingTalk provider 由独立 dingtalk profile 显式启用,Compose/Kubernetes 配置透传已补齐。
  • 新增 token、claims、user-info、授权请求和 callback 集成测试,并保留 GitHub/GitLab/OIDC 路由回归断言。

当前仍不直接合并:

  1. 该 HEAD 的外部 workflow 仍是 action_required,没有新的完整 PR Tests / E2E / Docs 结果;请先批准运行 workflow。
  2. 尚未在香港测试机按 big-main 隔离镜像部署验证。需要覆盖默认关闭、显式 profile 开启、授权跳转、token expiresIn、自定义 user-info header、unionId/openId fallback、失败脱敏和现有 8080 服务不受影响。
  3. 在上述 CI 与香港验证完成前维持 Changes Requested;不向外部贡献分支追加维护者 commit,也不触碰 main

@XiaoSeS

XiaoSeS commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

补充一个当前 HEAD 的范围/仓库规则问题:

  • document/docs/02-administration/...document/i18n/... 仍有手工 diff(删除旧 DingTalk 配置段)。仓库规则将 document/docs/ 标为生成目录;请只改权威源并通过文档生成流程产出,或在 PR 说明中证明这些目录确实是本项目的可编辑源,避免生成文件漂移。
  • server/skillhub-app/src/main/resources/application-local.yml 同时改变全局日志级别,SkillStorageDeletionCompensationJpaRepository 也有与 DingTalk 无关的可见性变更。除非有明确运行时依赖,否则建议拆出或回退,收窄 PR diff。

这两项不改变前面 OAuth 代码修复的判断,但在完整 CI 与 big-main 集成前需要收口。

@XiaoSeS

XiaoSeS commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

在最新 big-main(a34018fcc7232eca06e63e1c281fb874d863e5b8)上做了隔离合并演练,维护者集成提交为 e4cb61a50d7f3d69bd628a9a1dbe3c1accf7349e,未推送、未合并 main

当前阻塞是编译级兼容性问题:

  • DingTalkClaimsExtractor.java 引用了当前基线已不存在的 OAuthClaims
  • DingTalkOAuth2UserService.java 同样引用 OAuthClaims
  • 当前统一身份核心要求适配器返回 ProviderAuthenticationResult,并填充 SubjectCandidateProviderAttributeValueProtocolAuthenticationEvidence,再通过 OAuthLoginFlowService.authenticate(ClientRegistration, result, IdentityLoginContext) 完成登录/绑定流程。

因此本轮 make test-backend-appskillhub-auth 编译阶段失败,尚未构建镜像或部署香港测试机。请将 DingTalk 适配器迁移到当前身份核心 API 后再请求复审;我没有修改你的作者分支。

@konglong87

Copy link
Copy Markdown
Author

在最新 big-main(a34018fcc7232eca06e63e1c281fb874d863e5b8)上做了隔离合并演练,维护者集成提交为 e4cb61a50d7f3d69bd628a9a1dbe3c1accf7349e,未推送、未合并 main

当前阻塞是编译级兼容性问题:

  • DingTalkClaimsExtractor.java 引用了当前基线已不存在的 OAuthClaims
  • DingTalkOAuth2UserService.java 同样引用 OAuthClaims
  • 当前统一身份核心要求适配器返回 ProviderAuthenticationResult,并填充 SubjectCandidateProviderAttributeValueProtocolAuthenticationEvidence,再通过 OAuthLoginFlowService.authenticate(ClientRegistration, result, IdentityLoginContext) 完成登录/绑定流程。

因此本轮 make test-backend-appskillhub-auth 编译阶段失败,尚未构建镜像或部署香港测试机。请将 DingTalk 适配器迁移到当前身份核心 API 后再请求复审;我没有修改你的作者分支。

感谢您的复核和集成验证!

我注意到后续 PR #677 已完成新版统一身份核心下的 DingTalk OAuth2 适配,并已合并到 big-main;对应 SHA 也已经通过 CI、制品构建和香港测试机隔离验证。

为避免在 #467 中重复实现相同适配器或产生新的合并冲突,想确认两点:

  1. feat(auth): add DingTalk OAuth2 adapter to unified identity core #677 是否已经正式替代本 PR?如果是,我可以将 feat(auth): add DingTalk OAuth2 login support #467 作为 superseded PR 关闭,后续只跟踪 Add DingTalk OAuth login provider #583 以及 big-mainmain 的晋升。
  2. Add DingTalk OAuth login provider #583 原要求 subject 按 unionId -> openId -> userId 回退,而 feat(auth): add DingTalk OAuth2 adapter to unified identity core #675/feat(auth): add DingTalk OAuth2 adapter to unified identity core #677unionId 设为必需的 primary subject,openId/userId 仅作为 alternate subjects。后续应以哪一项契约为准?

在您确认前我先不继续修改或强推 #467,避免重复代码。
如果仍需要保留本 PR,请您帮忙明确目标基线,我会据此收窄修改后再提交。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants