SKILL.md 18 KB


name: kdshrsso description: >- Design and implement Kingdee EAS/SHR single sign-on. Patterns: (A) override platform CAS/OAuth filters; (B) Servlet + LtpaToken (OAToSHR / IDM); (B-qy) Weaver E10 or DingTalk OAuth in qy.war then POST index2sso. Covers eassso/login_withForm.js, OAToSHR, CallbackSuffixCompatFilter, Apusic *.do callback 404, LTPA Location multi-encode fix, index2sso vs OTP2sso, logoutHR.js federated Weaver logout + service whitelist, AttackFilter CSRF/Referer, adSsoConfig, OASSOConfig, KDPortalAuthenticationFilter, WeaverAuthController vs UnifiedAuth. BEFORE coding: ask intake questions in this skill (IdP, pattern A/B/B-qy, env, mapping, logout).

When user says 上线/切生产/go-live: follow Go-live section; list files to change; confirm prod URLs before editing.

Kingdee SSO — Implementation Patterns

Pick one primary pattern per SHR SSO integration. Do not mix servlet Ltpa login with filter _const_cas_assertion_ on the same entry without explicit design.


Agent intake — ask before coding (mandatory)

When the user asks to implement or port SSO for a new project / new IdP, do not jump straight into code. First confirm goals with short questions (batch them). Mark unknowns; block only on items that change architecture.

Must confirm (block if unanswered)

  1. IdP type: 泛微 E10 / 钉钉 / 飞书 / 企业 IDM / Azure AD / other?有无接口或对接文档?
  2. Pattern: A(全站 /shr CAS 过滤)/ B(/shr/api/oAToSHR)/ B-qyqy.war /qy/auth/{platform})?不确定则按 IdP 用本 skill Quick decision 推荐后让用户确认。
  3. SHR / EAS 环境: shrServerName(协议+主机+端口)、是否 Apusic、是否已有或可部署 qy.war
  4. 人员映射: IdP 用户字段 ↔ sHR 哪一列(PERSON_NUMBER / cfidmusercode / 手机号 / …)?
  5. callbackUrl: 拟注册的完整回调(通常无 .do);与现网 host 是否一致?
  6. 门户入口: 是否改 eassso + login_withForm.js/shr/home.do 是否保留本地登录?
  7. 联邦退出: 退出后是否必须回 IdP 登录页再进 sHR?IdP 是否有 service/redirect 白名单

Should confirm (default OK if user says「按 BaoYPro」)

  1. 桌面进 sHR:默认 HTML POST index2sso(禁止 LTPA 进 Location)。
  2. PC 深链:默认 显式 /qy/auth/{platform}/app,不用 UA 版 Unified /auth/app
  3. 配置:INTEGRATION_CONFIG_PATH + integration-config-*.json;密钥不进 git。
  4. 与待办是否同期做?若是,一并加载 kdshrpmsg 并做其 intake(深链入口与 SSO 一致)。

Output before coding

Reply with a short 目标确认表(模式、入口 URL、callback、映射、退出、是否动门户 JS),等用户点头或补答案后再改代码。若用户已贴齐文档+明确「按 B-qy / 按报业」,可只复述假设请一次确认。


Go-live / 切生产 (mandatory when user says 上线)

For BaoYPro-style B-qy, do not silently rewrite files. First ask for production SHR URL, ecology URL, appId/secret, dataCenter. Then list and edit:

# File / config Change
1 set-server-env.sh (or equiv.) ENV=prodINTEGRATION_CONFIG_PATH=.../integration-config-prod.json;重启
2 integration-config-prod.json shrServerNamedataCenterallowedPrefixesappId/appSecretcallbackUrlweaver.ecologyUrl
3 login_withForm.js ENV_CONFIG.prod.* + getCurrentEnv() 识别生产 Host
4 logoutHR.js prod.shrBaseUrl / weaverAppIdcurrentEnv='prod'(或按 host)
5 泛微后台 生产 callback 注册;退出 service 白名单 = 生产 SHR 根地址

Also remind kdshrpmsg go-live for weaverTodoConfig.properties.
Full checklist: project docs/integration/go-live-prod.html.

验收:生产门户登录、退出后再进 SHR、地址栏无测试 IP。


Quick decision

Need SSO?
  │
  ├─ IdP is Azure AD / standard OIDC (POST token + userinfo)?
  │     └─ Option A (Filter overlay) — see azure-oauth-reference.md
  │
  ├─ IdP is enterprise IDM, portal/OA deep-links with todo URLs?
  │     ├─ Already on YHPro-style OAToSHR + index2sso? → stay Option B
  │     └─ Want platform-native SHR session, all /shr URLs auto-OAuth?
  │           └─ Option A with IDM TicketValidator — see idm-filter-reference.md
  │
  ├─ IdP is 泛微 E10 (or DingTalk) OAuth code flow into sHR?
  │     └─ Option B-qy: qy.war /qy/auth/{platform}/app + POST index2sso
  │           — prefer explicit /weaver or /dingtalk; avoid UA-only /auth/app for PC deep links
  │           — see weaver-oauth-qywar-reference.md
  │
  └─ Entry from EAS portal eassso/login?service=... ?
        ├─ Option A: portal links to SHR URL directly, or JS forwards service URL
        ├─ Option B: JS/servlet bridge to /shr/api/oAToSHR
        └─ Option B-qy: JS → /qy/auth/weaver/app.do?appId&redirect= (BaoYPro)

| | Option A: Filter overlay | Option B: Servlet + LtpaToken | Option B-qy: qy.war + index2sso | |--|------------------------------|-----------------------------------|-------------------------------------| | Entry | /shr?adsso or any SHR URL via CAS | /shr/api/oAToSHR (or portal JS) | /qy/auth/weaver/app (or /dingtalk/...) | | Callback | {host}/shr?code= | {host}/shr/api/oAToSHR?code= | {host}/qy/auth/callback/{platform}/{appType} | | Session | _const_cas_assertion_ | index2sso.jsp + Ltpa | Same as B: index2sso.jsp + Ltpa | | Config | adSsoConfig.properties | OASSOConfig.properties | integration-config*.json + INTEGRATION_CONFIG_PATH | | Deploy | Overlay platform filters | Servlet in shr war | lib/sp jar + qy.war | | Best for | Seamless /shr browse; Azure/OIDC | IDM + OAToSHR | 泛微 E10 / 钉钉 via custom integration |

Not covered: JSP-only redirect, ActiveDirAuthHandler (Kerberos), product default CAS ticket-only login.


End-to-end flows (compare)

Option A

Browser → /shr/... (or /shr?adsso)
  → KDPortalAuthenticationFilter → 302 IdP authorize
  → callback /shr?code=
  → KDPortalTicketValidationFilter → TicketValidator.validate()
  → _const_cas_assertion_ → onSuccessfulValidation → target URL

Option B (YHPro / IDM)

Portal → eassso/login?service=SHR_URL  [optional JS bridge]
  → /shr/api/oAToSHR?redirect=|service=SHR_URL
  → IDM authorize (state = encoded target)
  → callback /shr/api/oAToSHR?code=&state=
  → index2sso.jsp?username&password(Ltpa)&redirectTo
  → SHR session → target URL

Option B-qy (BaoYPro / Weaver E10)

Portal → eassso/login?service=SHR_URL
  → login_withForm.js → /qy/auth/weaver/app.do?appId&redirect=
  → 泛微 authorize → /qy/auth/callback/weaver/pa
  → Filter forward → pa.do → token/profile → sHR user
  → WeaverShrLanding HTML POST → /shr/index2sso.jsp
  → SHR session → redirectTo

Logout (federated): logoutHR.js/qy/auth/weaver/logout.do?appId&redirect= → E10 logout
service = SSO entry URL; Weaver 退出白名单 must include SHR host.

Full detail: weaver-oauth-qywar-reference.md


Option A — Platform Class Overlay

When to use

Scenario Fit
Azure AD / Office 365 OAuth2 Yes — use OAuth20CodeValidata
Enterprise IDM on all /shr URLs Yes — custom IdmCodeValidata (idm-filter-reference.md)
EAS CAS username/password only No
Kerberos/AD domain No — ActiveDirAuthHandler

Architecture

Platform: SHRTicketValidationFilter extends KDPortalTicketValidationFilter.

Override hooks:

  • retrieveTicketFromRequest() — treat OAuth code as ticket
  • getTicketValidator() / getTicketValidatorToSso() — return custom TicketValidator
  • onSuccessfulValidation() — user check, deep-link redirect via urlToMap

Checklist

- [ ] IdP URLs; client_id; redirect_uri = https://{host}/shr (not servlet path)
- [ ] adSsoConfig.properties + ConfigAddressUtil
- [ ] KDPortalAuthenticationFilter — authorize redirect; whitelist local login paths
- [ ] KDPortalTicketValidationFilter — code branch
- [ ] TicketValidator (OAuth20CodeValidata or IdmCodeValidata)
- [ ] onSuccessfulValidation — T_PM_USER check; urlToMap / state redirect
- [ ] Portal: direct SHR links or login button → /shr?adsso ([portal-login-reference.md](portal-login-reference.md))
- [ ] web.xml: Authentication Filter before Validation Filter
- [ ] Overlay deploy; restart; verify _const_cas_assertion_ in session

Config template (Azure/OIDC)

Path: {eas.properties.dir}/adSsoConfig.properties

authUrl=https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
tokenUrl=https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
client_id=
client_secret=
shrUrl=https://{host}/shr
userUrl=https://graph.microsoft.com/v1.0/me
dataCenter=
locale=L2
mobileEid=

Troubleshooting

Symptom Check
No redirect to IdP Filter deployed; flag whitelist; existing assertion
Callback fails Filter order; code in retrieveTicketFromRequest
User not found Mapping in TicketValidator; T_PM_USER.FNUMBER
Portal still shows login form eassso bridge not forwarding to SHR — see portal doc
Deep link lost urlToMap / state set before authorize redirect

Reference: azure-oauth-reference.md, idm-filter-reference.md


Option B — Servlet + LtpaToken (Portal / IDM)

When to use

Scenario Fit
Portal/OA todo deep links (?redirect=) Yes
IDM OAuth with servlet callback Yes
Federated logout JSON (idmLogoutUrl) Yes
All /shr browse should auto-OAuth without servlet No — use Option A
Azure AD on main /shr URL No — use Option A

Checklist

- [ ] OASSOConfig.properties (authorizePath, getAccessTokenPath, ssoUrl, idmBaseUrl, serverName)
- [ ] OAToSHR: authorize() + callBack() + generateShrLoginUrl()
- [ ] SsoUtil: config, getAccessToken (GET IDM), checkUserExist (cfidmusercode)
- [ ] SHRToIDMLogout + logoutHR.js (if IDM federated logout required)
- [ ] web.xml servlets; cas.client.UrlPattern excludes servlet + index2sso.jsp
- [ ] IDM redirect_uri = ssoUrl (/shr/api/oAToSHR)
- [ ] Portal link or login_withForm.js bridge
- [ ] person.cfidmusercode populated for all SSO users

Config template (IDM)

Path: {EAS_HOME}/server/properties/{customer}/OASSOConfig.properties

authorizePath=https://idm.example.com/oauth2/authorize
getAccessTokenPath=https://idm.example.com/token/codeInfo
client_id=shr
client_secret=
response_type=code
ssoUrl=https://shr.example.com/shr/api/oAToSHR
serverName=https://shr.example.com
idmBaseUrl=https://idm.example.com

Dynamic redirect (state)

state is dynamic per request, not fixed:

  1. redirect param (preferred) → URL-encoded into OAuth state
  2. fallback service param (CAS-style, used by eassso bridge)
  3. default homepage if both absent

Callback decodes stateindex2sso.jsp redirectTo.

Troubleshooting

Symptom Check
Servlet 500 on init OASSOConfig path; ssoUrl key present (not only redirect_uri1/2)
Token exchange fails GET vs POST API; redirect_uri match
User not found person.cfidmusercode vs IDM user_info.id
Redirect loop UrlPattern excludes /api/oAToSHR, /index2sso.jsp
Logout incomplete Frontend follows idmLogoutUrl

Reference: idm-servlet-reference.md


Option B-qy — Weaver E10 / DingTalk via qy.war (summary)

Session = Option B (index2sso + LTPA). OAuth runs in qy.war under /qy/auth/*.

BaoYPro prefers explicit /qy/auth/weaver/app(.do) (not UA-based Unified /auth/app).

Checklist

- [ ] integration jar in lib/sp; qy.war in easweb.ear + Filter + Spring config
- [ ] integration-config: callbackUrl == IdP register (no .do); ecologyUrl / appId / secret
- [ ] CallbackSuffixCompatFilter + Controller *.do mappings + suffix-pattern=false
- [ ] Landing: WeaverShrLanding / writeShrSsoLoginForm POST to index2sso (never LTPA in Location)
- [ ] Portal login_withForm.js → /qy/auth/weaver/app.do?appId&redirect=
- [ ] logoutHR.js → /qy/auth/weaver/logout.do?appId&redirect= + Weaver service whitelist
- [ ] AttackFilter: allow /qy/ (or accept CSRF risk) if external Referer blocks /qy
- [ ] Prefer index2sso.jsp over OTP2sso.jsp for OAuth LTPA

What actually fixed “must copy-paste URL to login”

Problem Root cause Fix
Callback /pa 404 Apusic needs *.do for external GET Filter forward to .do; map both paths
Paste address bar then login works LTPA in Location multi-encoded (%2525252B) Callback HTML POST form to index2sso — not GET Location
Logout then stay in Weaver service not on Weaver whitelist / wrong service Whitelist SHR host; service = SSO entry URL
External Referer → /qy 404 Apusic AttackFilter CSRF Whitelist /qy/ + Weaver host

Reference: weaver-oauth-qywar-reference.md


Portal bridge (eassso/login)

EAS portal sends users to eassso/login?service={target}. Patterns differ:

Pattern Portal JS behavior Who does OAuth
A — direct location.replace(serviceParam) → SHR URL SHR CAS filters
A — adsso button Link to /shr?adsso SHR CAS filters
B — servlet bridge location.replace(oaSsoUrl + '?redirect=' + encodeURIComponent(target)) OAToSHR servlet
B-qy /qy/auth/weaver/app.do?appId=&redirect= qy WeaverAuthController

Common whitelist: keep /shr/home.do on native eassso login for emergency local accounts.

Anti-patterns:

  • Hardcoding env URLs in JS — inject from server config/JSP when possible
  • Editing only runtime/.../sso_web.war without src overlay — lost on redeploy
  • needRedirect when service empty — may force SSO on bare eassso/login
  • Mixing Option A filters with Option B OAToSHR on same entry
  • Putting LTPA password in 302 Location on Apusic (multi-encode)

Full detail: portal-login-reference.md, weaver-oauth-qywar-reference.md


Logout

Pattern Mechanism
Option A Platform /eassso/logout chain; extend for IdP if needed
Option B (IDM) SHRToIDMLogout → JSON { idmLogoutUrl }; logoutHR.js redirects browser
Option B-qy (Weaver / BaoYPro) logoutHR.js/qy/auth/weaver/logout.do?appId&redirect= → E10 /sp/integration/oauth2.0/logout?service= where service = SHR SSO entry; Weaver 退出白名单 must include SHR host
IDM spec {idmBaseUrl}/token/code-logout?redirect_url={encoded authorize URL}

Keep logout URL config in one place where possible. BaoYPro: JS holds test/prod weaverAppId + shrBaseUrl; server builds SSO entry as service and HTML-encodes once (WeaverShrLanding.writeLogoutThenService).

If address bar after logout shows service=http://{ecology}/, whitelist failed or old package — user will stay in Weaver after login.


Option A ↔ Option B migration

From → To Key changes
B → A IDM redirect_uri: /api/oAToSHR/shr; implement IdmCodeValidata; remove OAToSHR from login path; portal JS forwards to SHR URL not servlet; replace index2sso with assertion session
A → B Register OAToSHR; exclude paths in UrlPattern; IDM redirect_uri → servlet; portal JS → OAToSHR; add SHRToIDMLogout
B ↔ B-qy Different OAuth host (IDM servlet vs qy.war); same index2sso landing; portal JS target and IdP redirect_uri must match chosen pattern

Can run dual entry during migration if IdP registers two redirect_uri values; avoid long-term dual login paths.

Detail: migration-guide.md


Config field mapping (IDM)

OASSOConfig (Option B) adSsoConfig (Option A) Notes
authorizePath authUrl IDM authorize endpoint
getAccessTokenPath tokenUrl B: GET query; A: may need IdmCodeValidata
ssoUrl shrUrl + redirect_uri B uses servlet path; A uses /shr
serverName shrUrl host SHR base URL
idmBaseUrl (logout only) code-logout endpoint
client_id / client_secret same
userUrl Not needed if token response includes user_info

B-qy uses integration-config*.json (callbackUrl, ecologyUrl, appId) instead of OASSOConfig — see weaver reference.


Search terms

# Option A
KDPortalAuthenticationFilter, KDPortalTicketValidationFilter, OAuth20CodeValidata
ConfigAddressUtil, adSsoConfig, adsso, SHRTicketValidationFilter

# Option B
OAToSHR, SsoUtil, SHRToIDMLogout, OASSOConfig, index2sso.jsp
LtpaTokenManager, cfidmusercode

# Option B-qy
WeaverAuthController, WeaverShrLanding, CallbackSuffixCompatFilter
/qy/auth/weaver/app, /qy/auth/weaver/logout, /qy/auth/callback/weaver
integration-config, ecologyUrl, AttackFilter, serverNameWhiteList
logoutHR.js, writeLogoutThenService, index2sso.jsp
OTP2sso.jsp (do not use for Weaver OAuth LTPA)

# Portal
login_withForm.js, login_withForm.jsp, eassso/login, service, logoutHR.js
ssoParamemter.properties, cas.client.UrlPattern

References

Doc Content
azure-oauth-reference.md Option A — Azure AD / GYPro
idm-filter-reference.md Option A — IDM TicketValidator
idm-servlet-reference.md Option B — OAToSHR / YHPro
weaver-oauth-qywar-reference.md Option B-qy — Weaver E10 / qy.war + POST index2sso
portal-login-reference.md eassso/login_withForm.js bridge
migration-guide.md A ↔ B migration steps