From 50e7427596d7464fe1c35fda5599454de476e5a0 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:20:24 +0800 Subject: [PATCH] docs(deploy): complete DingTalk private deployment guide Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- .env.release.example | 2 + charts/skillhub/README.md | 2 + docs/03-authentication-design.md | 5 +- docs/09-deployment.md | 125 +++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 2 deletions(-) diff --git a/.env.release.example b/.env.release.example index ca295b72..505c594a 100644 --- a/.env.release.example +++ b/.env.release.example @@ -142,6 +142,8 @@ OAUTH2_FEISHU_DISPLAY_NAME=飞书 # button off the login page. Use the app's AppKey as the client id and AppSecret as the secret. # Like Feishu, DingTalk returns an organization-recorded email without attesting ownership, so # emailVerified is always false and the EMAIL_DOMAIN access policy would reject every login. +# The DingTalk console's server egress IP must be the real public IP of the backend calling +# api.dingtalk.com. A reverse tunnel only changes callback ingress and does not change egress. OAUTH2_DINGTALK_CLIENT_ID= OAUTH2_DINGTALK_CLIENT_SECRET= OAUTH2_DINGTALK_AUTHORIZE_URI=https://login.dingtalk.com diff --git a/charts/skillhub/README.md b/charts/skillhub/README.md index 1e8bc1a3..d9ffaa59 100644 --- a/charts/skillhub/README.md +++ b/charts/skillhub/README.md @@ -110,6 +110,8 @@ helm -n skillhub upgrade -i skillhub ./charts/skillhub \ | `skillhub-download-anon-cookie-secret` | 是 | 至少 32 字符的匿名下载 Cookie 签名密钥 | | `oauth2-github-client-id` | 否 | GitHub OAuth2 Client ID | | `oauth2-github-client-secret` | 否 | GitHub OAuth2 Client Secret | +| `oauth2-dingtalk-client-id` | 否 | DingTalk AppKey | +| `oauth2-dingtalk-client-secret` | 否 | DingTalk AppSecret | | `skill-scanner-llm-api-key` | 否 | Scanner LLM API Key | | `skill-scanner-llm-base-url` | 否 | Scanner 自定义 LLM API 地址 | | `skill-scanner-llm-model` | 否 | Scanner LLM 模型名称 | diff --git a/docs/03-authentication-design.md b/docs/03-authentication-design.md index 1d264616..19f6140f 100644 --- a/docs/03-authentication-design.md +++ b/docs/03-authentication-design.md @@ -287,7 +287,8 @@ spring: client-secret: ${OAUTH2_DINGTALK_CLIENT_SECRET} # 故意不声明 scope:钉钉的授权端点要 scope=openid,但在这里声明会让 # Spring 把该注册当成 OIDC 客户端并附加 nonce,而钉钉不接受 nonce。 - # scope 由 DingTalkAuthorizationRequestCustomizer 在请求阶段补上。 + # scope=openid 与 prompt=consent 由 DingTalkAuthorizationRequestCustomizer + # 在请求阶段补上。 # 钉钉是 confidential client,只是由自定义 token client 把 secret 放进 JSON body。 # 不使用 none,避免 Spring 自动添加本实现无法应答的 PKCE challenge。 client-authentication-method: client_secret_post @@ -315,7 +316,7 @@ Spring Security OAuth2 Client 原生支持多 Provider 并存,新增 Provider | 偏离环节 | 策略接口 | 现有实现 | |---|---|---| -| 授权请求参数 | `ProviderAuthorizationRequestCustomizer` | 钉钉补 `openid` scope | +| 授权请求参数 | `ProviderAuthorizationRequestCustomizer` | 钉钉补 `scope=openid` 与 `prompt=consent` | | token 交换 | `ProviderTokenResponseClient` | 钉钉用 JSON body 而非表单 | | userinfo 加载 | `ProviderOAuth2UserService` | 飞书拆信封;钉钉用自定义 token header | diff --git a/docs/09-deployment.md b/docs/09-deployment.md index e36f665c..452f442b 100644 --- a/docs/09-deployment.md +++ b/docs/09-deployment.md @@ -380,6 +380,131 @@ services: 登录仍绑定同一 `unionId`。上游失败时日志只记录 HTTP 状态、错误码、requiredScopes 和 requestId,不记录 AppSecret、authorization code、access token、unionId 或完整错误正文。 没有钉钉测试应用凭据时,这些只能标记为“协议测试通过、真实厂商往返未验证”。 + +### 7.1 钉钉配置示例 + +以下示例中的 `AppKey`、`AppSecret`、公网地址和出口 IP 都必须替换为部署环境的真实值。 +不要把 `AppSecret` 提交到 Git、镜像或 HTML 报告。 + +#### Docker Compose release + +在受保护的 `.env.release` 中设置: + +```dotenv +# 浏览器访问地址,不带末尾斜杠 +SKILLHUB_PUBLIC_BASE_URL=https://skills.example.com +SESSION_COOKIE_SECURE=true + +# 钉钉企业内部 H5 微应用 +OAUTH2_DINGTALK_CLIENT_ID=dingxxxxxxxx +OAUTH2_DINGTALK_CLIENT_SECRET=<从密钥管理系统注入> +OAUTH2_DINGTALK_REDIRECT_URI=https://skills.example.com/login/oauth2/code/dingtalk +OAUTH2_DINGTALK_AUTHORIZE_URI=https://login.dingtalk.com +OAUTH2_DINGTALK_BASE_URI=https://api.dingtalk.com +OAUTH2_DINGTALK_DISPLAY_NAME=钉钉 +``` + +启动和检查: + +```bash +make validate-release-config +docker compose --env-file .env.release -f compose.release.yml up -d +curl -fsS http://127.0.0.1:8080/actuator/health +curl -fsS http://127.0.0.1:8080/api/v1/auth/methods +``` + +钉钉后台必须同时配置: + +1. “钉钉登录与分享”回调 URL:与 `OAUTH2_DINGTALK_REDIRECT_URI` 完全一致。 +2. `Contact.User.Read` 个人信息读取权限,并将应用发布到当前版本。 +3. 服务器出口 IP:填写运行 SkillHub 后端并访问 `api.dingtalk.com` 的真实公网出口。 +4. 测试账号必须属于应用所属组织,并在应用可用范围内。 + +#### Helm 私有化部署 + +推荐使用 Kubernetes Secret,不把密钥写入 `values-production.yaml`: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: skillhub-production-secret + namespace: skillhub +type: Opaque +stringData: + bootstrap-admin-password: "<固定随机密码>" + skillhub-download-anon-cookie-secret: "<至少32字符随机值>" + oauth2-dingtalk-client-id: "dingxxxxxxxx" + oauth2-dingtalk-client-secret: "<从密钥管理系统注入>" +``` + +`values-production.yaml` 只放非敏感配置: + +```yaml +images: + registry: ghcr.io/iflytek + tag: <固定发布版本> + pullPolicy: IfNotPresent +publicBaseUrl: https://skills.example.com +session: + cookieSecure: true +ingress: + enabled: true + className: nginx + hosts: + - host: skills.example.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - skills.example.com + secretName: skillhub-tls +oauth2: + dingtalk: + authorizeBaseUri: https://login.dingtalk.com + apiBaseUri: https://api.dingtalk.com + redirectUri: https://skills.example.com/login/oauth2/code/dingtalk + displayName: 钉钉 +``` + +安装或升级: + +```bash +kubectl create namespace skillhub --dry-run=client -o yaml | kubectl apply -f - +kubectl apply -f skillhub-production-secret.yaml +helm upgrade --install skillhub ./charts/skillhub \ + --namespace skillhub \ + -f values-production.yaml \ + --set existingSecret=skillhub-production-secret +``` + +如果使用 Chart 自己创建 Secret,也可以在受保护的 values 文件中设置 +`secrets.oauth2DingtalkClientId` 和 `secrets.oauth2DingtalkClientSecret`;生产环境优先使用 +External Secrets、Sealed Secrets 或其他密钥注入方案。 + +#### 原生 Kubernetes/Kustomize + +在 `deploy/k8s/base/secret.yaml.example` 对应的 Secret 中提供: + +```yaml +stringData: + oauth2-dingtalk-client-id: dingxxxxxxxx + oauth2-dingtalk-client-secret: "<从密钥管理系统注入>" +``` + +再通过环境变量或 overlay 设置公开地址和回调: + +```yaml +env: + - name: SKILLHUB_PUBLIC_BASE_URL + value: https://skills.example.com + - name: OAUTH2_DINGTALK_REDIRECT_URI + value: https://skills.example.com/login/oauth2/code/dingtalk +``` + +Kubernetes 集群节点或出口网关的公网 IP 必须加入钉钉服务器出口 IP 白名单。Ingress 只负责浏览器 +回调可达性,不会替代后端出站 IP 白名单。 - 如果要启用密码重置验证码邮件,参见:`docs/19-smtp-password-reset-email-setup.md` ## 8 OIDC 登录配置