Okta Workforce Identity 组织、授权与生命周期手册
一个团队在 Okta 中创建 OIDC 应用,浏览器登录顺利,后端也能解出 JWT,于是把同一 access token 交给自建 API。测试偶尔成功,换到另一套 org 后却全部被拒绝。问题不是 JWT 库,而是把 org authorization server 发给 Okta API 的令牌当成了自建资源令牌;issuer、audience、scope 和资源服务器从一开始就没有对齐。
另一次离职操作中,管理员停用了 Okta user,应用分配也消失了,下游 SaaS 的旧会话却仍在运行。更糟的是,另一个组仍通过间接 assignment 保留访问,业务系统自己的 API token 也未回收。身份目录、应用分配、SCIM 账号状态、令牌策略和业务会话各自保存状态,可靠的生命周期管理必须逐层对账,而不是把“用户已停用”当作终点。
org 是对象、策略和日志的根容器
Okta org 是用户、组、app integration、Identity Provider、授权服务器、策略和日志的硬边界。相同邮箱在两个 org 中是两个独立主体,组名和应用标签也没有跨 org 的稳定含义。业务数据库保存 Okta 身份时,应使用 issuer/org + user id 形成复合键;只存 email 会在合并、改名或多组织接入时误绑账号。Okta organization 概念给出了 org 的隔离模型。
生产、预览、区域 cell、原始域名与 custom domain 会影响 base URL、issuer、证书和运维入口。服务不要从公司名猜 org URL,也不要把开发 org 的域名编进镜像。部署时从受控配置读取 issuer,通过 discovery 获取 endpoint 与 JWKS,并把允许的 issuer 作为环境级安全配置。custom domain 切换不仅是品牌变更,它可能改变 issuer、cookie、回调与受信证书,必须按身份迁移处理。
测试 org 适合验证协议与对象模型,但可用能力、限额、闲置策略和支持权益会变化。建立环境时从 Okta 当前开发者入口和目标合同确认可用产品,生产设计不依赖免费环境的默认行为。旧教程若依赖已弃用的 CLI 或历史开发者 org,应改用 Admin Console、Management API、官方 SDK 或受维护的 IaC provider,并锁定所使用的 provider 版本。
set -euo pipefail
: "${OKTA_ISSUER:?set the approved issuer}"
curl -fsS "${OKTA_ISSUER}/.well-known/openid-configuration" \
| tee /tmp/okta-discovery.json \
| jq '{issuer,authorization_endpoint,token_endpoint,jwks_uri,scopes_supported}'
test "$(jq -r '.issuer' /tmp/okta-discovery.json)" = "${EXPECTED_ISSUER}"
curl -fsS "$(jq -r '.jwks_uri' /tmp/okta-discovery.json)" | jq '{keyCount:(.keys|length)}'app integration 不是外部应用本身
app integration 描述 Okta 与外部应用之间的连接,可以承载 OIDC、SAML、SWA 或 provisioning 配置。它不是外部 SaaS 的业务实体,也不自动包含该 SaaS 中已有的角色和数据。用户或组被分配到 app 后,还会形成 app user、application group assignment 和 app-specific profile;组分配可以有 priority,最终 profile 可能由多个来源合成。
OIDC integration 中的 client ID、redirect URI、grant type、登录策略和 credential 只描述协议客户端。SAML integration 则围绕 entity ID、ACS、metadata、签名证书和属性映射。SCIM connector 又有独立 endpoint、credential、schema 与生命周期动作。三个能力即使挂在同一个 app integration 下,也应分别验证和撤销。应用清单至少记录 app ID、协议类型、owner、assignment 来源、credential 归属、profile source、下游业务 owner 和退出负责人。
读取 app 时不要只看顶层 status。下面的只读查询把 app、组 assignment、用户 assignment 和 scope grant 分开输出。管理 token 只授予所需读权限,并从密钥系统注入,不能把真实 token 放进 shell 历史。
set -euo pipefail
: "${OKTA_ORG_URL:?set the original org URL}"
: "${OKTA_MGMT_TOKEN:?use an approved read-only token}"
: "${OKTA_APP_ID:?set the app integration id}"
auth="Authorization: Bearer ${OKTA_MGMT_TOKEN}"
curl -fsS -H "$auth" "${OKTA_ORG_URL}/api/v1/apps/${OKTA_APP_ID}" \
| jq '{id,label,status,signOnMode,accessibility,credentials}'
curl -fsS -H "$auth" "${OKTA_ORG_URL}/api/v1/apps/${OKTA_APP_ID}/groups" \
| jq '.[] | {id,priority,profile}'
curl -fsS -H "$auth" "${OKTA_ORG_URL}/api/v1/apps/${OKTA_APP_ID}/users" \
| jq '.[] | {id,status,scope,credentials,profile}'
curl -fsS -H "$auth" "${OKTA_ORG_URL}/api/v1/apps/${OKTA_APP_ID}/grants" | jq .用户既可能被直接分配,也可能经多个组分配。移除一个来源时,剩余来源仍可能让 app user 保持 active;排障应查询 assignment scope 和相关 System Log,而不是看到一次 unassign 事件就断言访问已撤销。
先选对 authorization server 再谈 scope
每个 org 有 org authorization server,它主要为 Okta 自身资源签发访问令牌。其 access token 的 audience 和 claims 由 Okta 管理,自建 resource server 不应把它当稳定业务契约。自建 API 要使用 custom authorization server,由团队定义 audience、scope、claim 和 access policy;每个 custom server 拥有独立 issuer 和签名 key。授权服务器说明明确了两类服务器的用途差异。
因此“Okta 签发的 JWT”不是充分条件。资源端必须精确验证 issuer、audience、签名和时间字段,再检查 scope 或业务 claim。一个 token 发给 Okta Management API,就不应被订单 API 接受;同名 read scope 出现在两个 authorization server 中,也不是同一权限。项目应把 issuer、resource audience 和授权策略版本放在同一份部署契约中,变更任一项都触发正反回归。
custom authorization server 的 access policy 决定哪些 client、用户、grant type 和 scope 能得到 token。规则顺序与条件遗漏会造成“测试 client 成功,其他 client 也意外成功”。最小策略先只允许 canary client、明确 grant type 和应用专用 scope,再用未列入策略的 client 做反例。若反例仍拿到 token,应检查命中了哪条 rule,而不是继续在资源端增加补丁式判断。
consent、scope grant 与管理角色是三道门
面向用户的 OIDC consent 由 client 的 consent 设置、authorization server scope 的 consent 属性、请求参数和已有用户 grant 共同决定。用户看过同意页不代表管理员授权,也不代表应用获得 Okta 管理权限。撤销用户 consent 后必须重新获取 token 观察变化,不能复用旧 access token 判断撤销是否生效。
机器调用 Okta Management API 通常使用 OAuth service app 和 private_key_jwt。service app 首先需要被 grant 对应 Okta API scope,然后还要获得足够的 admin role;使用自定义 admin role 时,再由 resource set 限制它能管理哪些对象。scope 决定可调用哪类 API,role/resource set 决定能操作哪些资源。只有 scope 没 role,资源调用应 403;有 role 没 scope,token 请求或授权结果应拒绝。Okta 的OAuth service app 指南展示了这条对象链。
私钥签名断言时,iss 和 sub 通常关联 client,aud 指向 token endpoint,kid 对应已注册 JWKS。断言应短期有效且防重放,私钥保存在 KMS、HSM 或密钥系统中。轮换先发布新公钥并验证新 kid,再切换签名方,观察旧 key 使用归零后撤销旧 key;不要覆盖唯一 key 后期待所有运行实例同步完成。
set -euo pipefail
: "${OKTA_ORG_URL:?set org URL}"
: "${SERVICE_CLIENT_ID:?set service app client id}"
: "${CLIENT_ASSERTION:?generate outside the command history}"
curl -fsS -X POST "${OKTA_ORG_URL}/oauth2/v1/token" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode "client_id=${SERVICE_CLIENT_ID}" \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode "client_assertion=${CLIENT_ASSERTION}" \
--data-urlencode "scope=${REQUESTED_OKTA_SCOPES}" \
| jq '{token_type,scope,expires_in}'命令输出不能进入持久 CI 日志。更好的流水线把 token 直接送入进程内存,并只记录 token 指纹、client、issuer、scope 集合与请求 correlation ID。
group、规则和 app profile 要避免隐式提权
Okta group 既可表达目录组织,也可做 app assignment、授权策略条件、groups claim、管理员角色来源和 SCIM Group Push。将“部门组”直接当“生产管理员组”,会让 HR 调整或 group rule 变化触发权限扩散。应用专用组应使用稳定 ID 进入策略,命名只承担可读性;高权限组禁止由宽松的属性规则自动加入,并对 rule activation、membership change 与 app assignment 建立审计告警。
groups claim 受授权服务器、token 类型、claim filter 和 Okta Expression Language 影响。资源端只接受白名单 claim 和明确 issuer,不按任意组名前缀自动提升权限。令牌里没有某组时,先检查 claim 绑定在哪个 authorization server、过滤表达式是否命中以及请求 token 类型,而不是直接把全部组塞进 token。令牌体积会穿过代理、cookie 和 header 限制,组越多也越容易泄露组织结构;应用角色或细粒度 entitlement 往往比复制完整目录更稳定。
多个 group assignment 还可能竞争 app user profile。测试时让 canary 从两个低风险组得到不同的测试属性,读取最终 app user profile 和 assignment priority,再移除一个来源观察回退。预期结果必须与目标 connector 的 profile sourcing 规则一致;若顺序不明确,就不要让同一敏感属性由多个组写入。
SCIM 同步要用完整状态链验收
Okta provisioning 可以创建、更新、停用、导入用户,也可能支持 Group Push;实际动作取决于 app integration、connector profile 与目标 SCIM 实现。新集成以 SCIM 2.0 的 RFC 7643/7644 为契约基线,但 Okta 和目标服务都可能只采用其中一部分。先确认 schema、filter、PATCH、pagination、Group、active、matching attribute 和错误响应,再启用对应能力;不使用的 import 或 push 不要顺手开启。
set -euo pipefail
: "${SCIM_BASE_URL:?set the isolated SCIM endpoint}"
: "${SCIM_TOKEN:?load the connector credential securely}"
auth="Authorization: Bearer ${SCIM_TOKEN}"
curl -fsS -H "$auth" "${SCIM_BASE_URL}/ServiceProviderConfig" | jq .
curl -fsS -H "$auth" "${SCIM_BASE_URL}/ResourceTypes" | jq '.Resources[] | {id,endpoint,schema}'
curl -fsS -G -H "$auth" \
--data-urlencode 'filter=userName eq "okta-canary@example.invalid"' \
"${SCIM_BASE_URL}/Users" | jq '{schemas,totalResults,Resources}'
code=$(curl -sS -o /tmp/scim-denied.json -w '%{http_code}' \
-H 'Authorization: Bearer invalid' "${SCIM_BASE_URL}/Users")
test "$code" = "401" -o "$code" = "403"正向链从单个 canary 开始:assign 后确认 create 与 externalId,更新一个非敏感属性,加入和移出测试组,unassign 后确认目标收到约定的 deactivate,重新 assign 再确认 rehire 或 recreate 行为。反向链制造重复 matching attribute、非法 filter、目标 429 和 5xx,验证任务可见失败、遵循响应提示退避且不会静默全量覆盖。连接测试成功只证明 credential 和端点可用,不证明这些状态转换正确。
停用下游用户后,还要撤业务会话、个人 API token、应用内 entitlement 和许可证。Group Push 管的是目标组及成员关系,不等于给 Okta app 分配该组;把两种“组”操作合并在一个自动化函数里,最容易造成误删和访问残留。
项目接入从 discovery 到业务授权逐层收口
Web 应用使用授权码加 PKCE,服务端保存 state、nonce 和原始跳转目标;回调只允许精确登记的 URI。API 资源端通过 discovery 和 JWKS 验签,同时校验 issuer、audience、有效期与 client 上下文,再把应用专用 scope、groups claim 或自定义 claim 映射为业务权限。ID token 用于客户端确认登录身份,access token 才交给目标 API,二者不能互换。
下面的资源端配置把 issuer 和 audience 都放在环境契约中。应用代码还应实现 audience validator,并把 Okta user ID 映射到内部主体;多 org 模式按 issuer 选择验证器和数据分区,不接受“任意 *.okta.com issuer”。
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${OKTA_AUTHORIZATION_SERVER_ISSUER}
identity:
expected-audience: ${OKTA_API_AUDIENCE}
allowed-issuers: ${OKTA_ALLOWED_ISSUERS}
required-scope: ${OKTA_REQUIRED_SCOPE}应用内主体表保存 issuer + Okta user id、内部 user ID、状态和授权版本,不用 email 自动合并。首次登录若尚未 provision,应进入明确的邀请或审批路径;若业务要求“先 SCIM 后登录”,资源端就拒绝目录中不存在的 app user。反之,采用即时创建时也要给出最低权限,不能因为 OIDC 登录成功就默认加入所有项目。
会话 cookie 使用 Secure、HttpOnly、SameSite 和服务端会话版本控制。用户停用、组移除或风险事件发生时,应用接收事件或定期对账,提升会话版本使旧会话失效。只依赖 access token 自然过期会留下撤销窗口;对高风险操作可缩短令牌生命周期、执行实时策略检查或要求重新认证,但阈值由业务风险与可用性预算决定。
最小正反实验要跨 token 和资源 API
正向实验只分配一个 canary 用户与应用专用组。完成登录后,调用 /identity/whoami,输出经过脱敏的 issuer、subject、audience、scope 和角色;随后在 System Log 中按 transaction 或 request 标识找到认证事件,在应用日志中找到同一个 correlation ID。允许端点成功、未授权端点 403,证明认证与业务授权被分开执行。
第一组反例把 issuer 改成另一个测试 org,资源端必须在 issuer 校验处拒绝;第二组使用 org authorization server token 调用自建 API,必须因 audience 或 issuer 契约不符拒绝;第三组从 access policy 移除 canary client,token endpoint 应拒绝请求;第四组保留 scope grant 但撤掉 service app 的 admin role,Management API 应 403。每个失败都要同时保留 token endpoint、资源端和 System Log 证据。
set -euo pipefail
: "${ACCESS_TOKEN:?obtain a canary access token}"
: "${API_BASE_URL:?set the isolated test API}"
curl -fsS -H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "X-Correlation-ID: ${CORRELATION_ID}" \
"${API_BASE_URL}/identity/whoami" \
| jq '{issuer,subject,audience,scopes,roles}'
status=$(curl -sS -o /tmp/okta-denied.json -w '%{http_code}' \
-H "Authorization: Bearer ${WRONG_ISSUER_TOKEN}" \
"${API_BASE_URL}/identity/whoami")
test "$status" = "401" -o "$status" = "403"
jq '{error,correlationId}' /tmp/okta-denied.json实验收尾先撤业务会话和 refresh token,再 unassign canary、撤 consent/scope grant、停用 SCIM connector、撤销测试 key,最后删除测试 app 与组。清理后重复旧 token、旧 key 和旧 SCIM credential 的负向调用;若仍成功,说明撤销链尚未闭合。
System Log 要能还原谁改变了什么
System Log 以事件模型记录认证、策略评估、应用分配、目录对象、provisioning 和管理操作。排障时关注 eventType、actor、target、outcome、transaction、debug context 和 request 信息,不只读自然语言消息。一个登录失败可能来自 client、authorization server policy、用户状态、factor 或网络区域,先按事件类型和 transaction 分层,才能找到第一处拒绝。
set -euo pipefail
: "${OKTA_ORG_URL:?set org URL}"
: "${OKTA_MGMT_TOKEN:?use a log reader token}"
: "${SINCE_RFC3339:?set the stored checkpoint time}"
curl -fsS -D /tmp/okta-log.headers \
-H "Authorization: Bearer ${OKTA_MGMT_TOKEN}" \
"${OKTA_ORG_URL}/api/v1/logs?since=${SINCE_RFC3339}&sortOrder=ASCENDING" \
| jq '.[] | {uuid,published,eventType,actor,outcome,target,transaction}'
grep -i '^link:' /tmp/okta-log.headers持续采集必须跟随服务返回的 Link: next,不要自己拼分页游标。事件以 uuid 去重,checkpoint 在持久化一批事件后再提交;重启时允许重复读取,不能越过尚未落盘的事件。采集器监控最后成功游标、迟到事件、重复率、解析失败、外送延迟与目标存储写入错误。
System Log 的可查询窗口、事件字段和外送产品能力会变化,长期审计应在事件仍可取时外送到受控存储。保存原始事件、哈希和必要索引,并用访问控制保护 IP、设备、用户 profile、factor 与请求细节。事件 hook 适合近实时触发,不是完整日志复制;接收方要验签或验证授权、幂等处理、快速响应并通过 System Log 补偿漏失事件。
凭证、数据与管理权限需要独立所有权
SSWS token 的权限和生命周期容易绑定创建者,长期自动化更适合 scoped OAuth service app、private_key_jwt 与最小 admin role/resource set。无论哪种凭证,都要记录 owner、用途、允许来源、轮换状态和撤销证据。私钥、client secret、SCIM credential、SAML private key、hook authorization header、access/refresh token 禁止进入仓库、工单、截图和构建日志。
管理员能力按职责拆分:应用团队管理自己的 integration 和 assignment,目录团队管理 profile source 与组规则,安全团队管理授权服务器、管理员角色和日志外送,业务 owner 管应用内 entitlement。break-glass 身份不参与日常自动化,使用强认证、审批、短时授权并定期演练。service app 即使只有有限 scope,也要用 resource set 继续限制对象;“机器账号”不是跳过职责分离的理由。
Universal Directory、app profile、System Log 和 SCIM payload 可能包含姓名、邮箱、部门、经理、位置、设备与风险信息。只同步应用真正需要的属性,敏感字段不进入 token,测试 org 使用合成身份。数据驻留、跨境、加密、删除、保留和支持访问由目标合同与部署区域决定,开发 org 的行为不能替代生产数据评审。
容量、成本和故障退化不能靠固定数字
API rate limit、并发、日志窗口、hook 能力、授权服务器权益、生命周期管理能力和支持等级都可能随 org、产品和合同改变。客户端从响应头和当前管理信息读取容量信号,对 429 遵循服务给出的恢复时间并加入抖动;写操作使用幂等键或先读后比,SCIM 与日志采集保存 checkpoint。不要把某个测试 org 观察到的数字写成全公司的永久常量。
成本评估要把活跃身份、应用集成、生命周期管理、API access management、安全策略、日志外送、支持、数据区域和迁移双跑放在一起。一个便宜但缺少所需授权服务器或 SCIM 治理能力的方案,会把成本转成自建同步器和人工核销;反过来,也不应因为测试 org 显示某功能就推断生产已购买。采购和架构评审直接核对当前产品页、合同、org 实际 entitlement 和支持承诺。
故障演练关注不变量:discovery 暂时不可用时不放宽 issuer,JWKS 轮换后缓存能恢复,System Log 采集重启不丢事件,SCIM 重试不重复建人,组规则批量变化能被暂停和回滚。告警使用相对基线,例如拒绝率突变、assignment 变化量偏离历史、checkpoint 长时间不前进或 deprovision 失败持续积压;具体阈值来自本 org 的 SLO 和容量测量。
撤销、清理和 org 迁移按依赖关系推进
人员离职先阻止新认证,再移除直接与组 assignment、管理员角色和高权限组,触发 SCIM deprovision,撤销 Okta session、refresh token 与用户 consent,最后回收业务会话、个人 API token、应用内 entitlement 和许可证。每一步在 System Log、目标 SCIM 日志和业务审计中留下对象 ID。仍有间接 group assignment 时,不能强行删除 app user 掩盖建模错误。
应用退出先冻结新 assignment 和 grant,盘点 app integration、app user、group assignment、authorization server policy、scope/claim、service app key、admin role/resource set、SCIM connector、hook 与日志订阅。确认替代路径承载真实流量后,先撤管理角色和 scope grant,再撤 key、停止 provisioning、删除 assignment 与 app,最后清理专用组和外送规则。旧凭证必须用负向调用证明失效。
org-to-org 或跨产品迁移不是改一个域名。issuer、user/app/group ID、client credential、签名 key、policy、consent、assignment、profile source 和 System Log 都要重建或转换;密码、MFA、设备、会话和应用内 entitlement 不能默认无损迁移。先建立旧、新主体映射,让单个 canary 应用双信任并比较 token、授权、SCIM 与日志,再按应用专用组逐批切换。冻结旧侧变更、完成最终增量和核销后撤旧 issuer;回退只保留受控期限内的旧信任,不能让两个 org 长期同时成为权威写源。
