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).
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.
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.
/shr CAS 过滤)/ B(/shr/api/oAToSHR)/ B-qy(qy.war /qy/auth/{platform})?不确定则按 IdP 用本 skill Quick decision 推荐后让用户确认。shrServerName(协议+主机+端口)、是否 Apusic、是否已有或可部署 qy.war?PERSON_NUMBER / cfidmusercode / 手机号 / …)?.do);与现网 host 是否一致?eassso + login_withForm.js?/shr/home.do 是否保留本地登录?index2sso(禁止 LTPA 进 Location)。/qy/auth/{platform}/app,不用 UA 版 Unified /auth/app。INTEGRATION_CONFIG_PATH + integration-config-*.json;密钥不进 git。Reply with a short 目标确认表(模式、入口 URL、callback、映射、退出、是否动门户 JS),等用户点头或补答案后再改代码。若用户已贴齐文档+明确「按 B-qy / 按报业」,可只复述假设请一次确认。
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=prod → INTEGRATION_CONFIG_PATH=.../integration-config-prod.json;重启 |
| 2 | integration-config-prod.json |
shrServerName、dataCenter、allowedPrefixes、appId/appSecret、callbackUrl、weaver.ecologyUrl |
| 3 | login_withForm.js |
ENV_CONFIG.prod.* + getCurrentEnv() 识别生产 Host |
| 4 | logoutHR.js |
prod.shrBaseUrl / weaverAppId;currentEnv='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。
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.
Browser → /shr/... (or /shr?adsso)
→ KDPortalAuthenticationFilter → 302 IdP authorize
→ callback /shr?code=
→ KDPortalTicketValidationFilter → TicketValidator.validate()
→ _const_cas_assertion_ → onSuccessfulValidation → target URL
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
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
| 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 |
Platform: SHRTicketValidationFilter extends KDPortalTicketValidationFilter.
Override hooks:
retrieveTicketFromRequest() — treat OAuth code as ticketgetTicketValidator() / getTicketValidatorToSso() — return custom TicketValidatoronSuccessfulValidation() — user check, deep-link redirect via urlToMap- [ ] 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
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=
| 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
| 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 |
- [ ] 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
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
state)state is dynamic per request, not fixed:
redirect param (preferred) → URL-encoded into OAuth stateservice param (CAS-style, used by eassso bridge)Callback decodes state → index2sso.jsp redirectTo.
| 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
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).
- [ ] 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
| 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
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:
runtime/.../sso_web.war without src overlay — lost on redeployneedRedirect when service empty — may force SSO on bare eassso/loginpassword in 302 Location on Apusic (multi-encode)Full detail: portal-login-reference.md, weaver-oauth-qywar-reference.md
| 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.
| 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
| 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.
# 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
| 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 |