# IDM Portal SSO �� Servlet + LtpaToken Reference (YHPro)
Reference for `kdshrsso` skill **Option B**. Adapt paths, IdP URLs, and user-mapping field per project.
Related: [portal-login-reference.md](portal-login-reference.md), [migration-guide.md](migration-guide.md)
## Source layout (YHPro)
```
src/com/kingdee/eas/custom/sso/
������ OAToSHR.java # SHR portal SSO entry servlet
������ SHRToIDMLogout.java # Federated logout (returns idmLogoutUrl JSON)
������ OAToMBos.java # Mobile MBOS SSO (separate config)
������ SsoUtil.java # Config, HTTP, user lookup, bill validation
������ utils/ExpiringMapCache.java
������ filter/authentication/PortalAuthenticationFilter.java # WIP �� not in web.xml
������ filter/validate/TicketValidationFilter.java # WIP �� not in web.xml
������ validation/PortalServiceTicketValidator.java # WIP validator
src/org/jasig/cas/client/Protocol.java # Protocol.YH("code","redirect_uri")
runtime/server/properties/yuhu/OASSOConfig.properties
runtime/server/deploy/easweb.ear/shr_web.war/WEB-INF/web.xml
runtime/server/deploy/easweb.ear/shr_web.war/WEB-INF/sso/ssoParamemter.properties
runtime/server/deploy/eas.ear/sso_web.war/common/js/login_withForm.js # portal bridge
runtime/server/deploy/easweb.ear/shr_web.war/scripts/gui/logoutHR.js # SHR logout
```
---
## web.xml (SHR servlets)
```xml
OAToSHR
com.kingdee.eas.custom.sso.OAToSHR
OAToSHR
/api/oAToSHR
SHRToIDMLogout
com.kingdee.eas.custom.sso.SHRToIDMLogout
SHRToIDMLogout
/api/SHRToIDMLogout
```
CAS filters unchanged (`casAuthenticationFilter` / `casTicketValidationFilter`). Exclude in `ssoParamemter.properties`:
```
cas.client.UrlPattern=...;/index2sso.jsp;...;/api/oAToSHR;/api/SHRToIDMLogout;/api/oatombos;
```
---
## Full call chain (with portal)
```
Portal CAS
�� eassso/login?service=https://shr.../shr/dynamic.do?...&AssignmentId=...
�� login_withForm.js intercept
�� /shr/api/oAToSHR?service=encodeURIComponent(service)
(portal may also use ?redirect= for direct links)
OAToSHR.doPost()
������ no code �� authorize()
�� dynamicRedirect = redirect | service | default homepage
�� state = URLEncoder.encode(dynamicRedirect) �� dynamic, not fixed
�� 302 �� IDM authorizePath?response_type=code&scope=server&client_id&state&redirect_uri=ssoUrl
������ code �� callBack()
decode state �� redirectUrl
GET getAccessTokenPath?code&client_id&client_secret&redirect_uri
user_info.id �� checkUserExist(cfidmusercode) �� T_PM_USER.FNumber
302 �� /shr/index2sso.jsp?username&password(Ltpa)&redirectTo
index2sso.jsp �� SHR session �� redirectUrl (todo / homepage)
```
---
## Dynamic redirect (`state`)
| Stage | Behavior |
|-------|----------|
| Input | `redirect` (1st) or `service` (2nd) on OAToSHR request |
| Authorize | `state = URLEncoder.encode(targetUrl)` �� **changes every request** |
| Callback | `URLDecoder.decode(state)` �� final redirectTo |
| Default | `{serverName}/shr/dynamic.do?uipk=shr.perself.homepage` |
---
## Logout chain
```
logoutHR.js
�� platform logout AJAX chain (/eassso/logout, shr_loginout, easweb/logout, ...)
�� clear cookies / storage
�� redirect to idmLogoutUrl (built in JS or from servlet �� prefer servlet)
SHRToIDMLogout (POST /shr/api/SHRToIDMLogout)
�� build IDM authorize URL with state = portal home
�� idmLogoutUrl = {idmBaseUrl}/token/code-logout?redirect_url={encoded authorize URL}
�� session.invalidate()
�� JSON { code:"1", idmLogoutUrl:"..." }
```
**Improvement:** have `logoutHR.js` call `SHRToIDMLogout` instead of duplicating IDM URL in JS.
---
## Config template
Path: `{EAS_HOME}/server/properties/yuhu/OASSOConfig.properties`
```properties
authorizePath=https://idm-test.example.com/oauth2/authorize
getAccessTokenPath=https://idm-test.example.com/token/codeInfo
client_id=shr
client_secret=
response_type=code
ssoUrl=https://shr-test.example.com/shr/api/oAToSHR
serverName=https://shr-test.example.com
idmBaseUrl=https://idm-test.example.com
lastRedirectUrl=/shr/home.do
```
**Production pitfall:** sample file had `redirect_uri1/2` for MBOS but `OAToSHR` requires **`ssoUrl`** (strict). Missing `ssoUrl` causes init/runtime errors.
---
## User mapping SQL
```sql
SELECT uinfo.FNumber AS userNum
FROM T_bd_person person
LEFT JOIN T_PM_USER uinfo ON person.fid = uinfo.FPERSONID
WHERE uinfo.FIsDelete = '0'
AND uinfo.FForbidden = '0'
AND person.cfidmusercode = ?
```
IDM field: `data.user_info.id` from token response.
---
## IDM token API
GET `{getAccessTokenPath}?code=&client_id=&client_secret=&redirect_uri=`
```json
{ "code": "0", "data": { "access_token": "...", "user_info": { "id": "...", "username": "..." } } }
```
---
## Portal login_withForm.js (YHPro bridge)
See [portal-login-reference.md](portal-login-reference.md) for full analysis.
Summary:
- IIFE at top of `login_withForm.js` runs before login form renders
- Whitelist `/shr/home.do` for native eassso login
- Other SHR paths �� `oaSsoUrl + "?service=" + encodeURIComponent(serviceParam)`
- **Anti-patterns found:** hardcoded ENV_CONFIG, runtime-only edit, prod URL placeholder, loose whitelist `includes()`
---
## OAToMBos (mobile, separate)
Uses `ExpiringMapCache` �� state carries cache key not full URL:
```
state = ssoUrl + "?redirect=" + cacheKey
```
Do not confuse with OAToSHR direct URL-in-state pattern.
---
## Deploy checklist
```
- [ ] Compile src/com/kingdee/eas/custom/sso/**
- [ ] Deploy to shr_web.war/WEB-INF/classes
- [ ] web.xml servlets + ssoParamemter.properties UrlPattern
- [ ] OASSOConfig.properties on server (include ssoUrl, idmBaseUrl)
- [ ] IDM redirect_uri = ssoUrl
- [ ] person.cfidmusercode for all SSO users
- [ ] login_withForm.js overlay in sso_web.war (src tracked, not runtime-only)
- [ ] logoutHR.js calls SHRToIDMLogout or shares config
- [ ] Test: portal todo, home.do whitelist, logout
```
---
## Troubleshooting
| Symptom | Check |
|---------|--------|
| 500 on servlet init | OASSOConfig missing; ssoUrl empty |
| authorize fails | authorizePath, client_id, IDM app registration |
| Token exchange fails | GET API; redirect_uri must match authorize |
| User not found | cfidmusercode vs user_info.id; duplicate rows |
| Login page flash | mask + replace(); onbeforeunload cleared |
| index2sso loop | UrlPattern excludes servlet + index2sso.jsp |
| Deep link wrong | state decode; redirect vs service param |
| Logout 401 IDM | idmLogoutUrl spec; redirect_url encoding |
---
## WIP filter code (not production)
`PortalAuthenticationFilter` + `TicketValidationFilter` + `PortalServiceTicketValidator`:
- Mirror Option A structure but **not wired in web.xml**
- Hardcoded test URLs; POST token vs GET in OAToSHR
- Use only as reference for [idm-filter-reference.md](idm-filter-reference.md) migration
Production login path: **OAToSHR servlet only**.