# LXCloud-IAM **Repository Path**: zhujinrun/lxcloud-iam ## Basic Information - **Project Name**: LXCloud-IAM - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-08 - **Last Updated**: 2026-08-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # lxcloud-iam 统一身份认证门户系统,面向多个业务子系统提供单点登录、应用门户、用户组织、角色权限、应用接入和审计能力。 ## 技术栈 - Java 21 - Spring Boot 3 - Spring Security - Spring Authorization Server - MyBatis Plus - Flyway - MySQL 8,本地开发默认数据库 - PostgreSQL,后续生产或多环境可选 - Redis ## 模块 - `iam-server`:认证授权服务和管理 API - `iam-client-demo`:最小 OAuth2 Client 示例,用于验证带 Client Secret 的子系统服务端回调 - `iam-ui`:统一门户前端,Vue 3 + TypeScript + Element Plus - `docs`:架构设计文档、子系统接入手册和生产配置清单 - `deploy`:部署配置 - `scripts`:开发和运维脚本 ## 本地启动 ```bash scripts/start-backend.sh ``` 基础配置位于 `iam-server/src/main/resources/application.yml`,本地默认值位于 `application-local.yml`,生产外部注入模板位于 `application-prod.yml`。启动脚本默认使用 JDK 21、工作区内 `.m2/repository`、`local` profile、`localhost` 绑定地址和 `28080` 端口。 默认使用工作区内 `.m2/repository` 是为了让本项目的依赖缓存、沙箱验证和本机全局 Maven 仓库相互隔离,减少不同项目之间的依赖版本污染。若确认要复用本机 Maven 仓库,可以通过 `MAVEN_REPO=/home/lxy/.m2/repository` 覆盖。 根目录提供 `.env.example` 作为本地联调模板。需要统一配置后端、前端和示例子系统时,可复制为 `.env`;脚本会自动加载根目录 `.env` 作为默认值,命令行显式传入的环境变量优先级更高。 ```bash cp .env.example .env ``` 可通过环境变量覆盖常用启动参数: ```bash PORT=8080 scripts/start-backend.sh BACKEND_PORT=8080 scripts/start-backend.sh HOST=0.0.0.0 scripts/start-backend.sh MAVEN_REPO=/home/lxy/.m2/repository scripts/start-backend.sh DB_URL='jdbc:mysql://localhost:3306/lxcloud_iam?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai' scripts/start-backend.sh REDIS_HOST=localhost REDIS_PASSWORD='' scripts/start-backend.sh IAM_ISSUER='http://localhost:28080' scripts/start-backend.sh IAM_TOKEN_SECRET='change-this-secret-with-at-least-32-bytes' scripts/start-backend.sh ``` 前端门户使用 Vue 3、TypeScript、Vite、Element Plus,风格参考 RuoYi-Vue-Plus 的 `plus-ui` 管理后台,但当前阶段保持轻量门户布局,优先打通应用入口和 OAuth2/OIDC 联调闭环。 8 个业务子系统接入时,可先按 `docs/subsystem-oidc-integration.md` 完成应用登记、客户端配置、回调联调和生产检查。手机验证码登录和短信供应商接入见 `docs/sms-provider-integration.md`。权限编码、资源映射、影响预览和审计追溯规则见 `docs/permission-governance-guide.md`。 进入测试、预生产或生产环境前,先按 `docs/production-readiness-checklist.md` 核对域名、HTTPS、Issuer、Secret、数据库、Redis、Flyway 和 OAuth2 Client 配置;部署操作参考 `docs/production-deployment.md`,宿主机 Nginx + systemd 发布和回滚步骤参考 `docs/production-release-runbook.md`,实际发布时可直接使用 `docs/production-host-release-checklist.md`;线上 SSO 验收参考 `docs/production-sso-smoke-checklist.md`,异常排查参考 `docs/production-troubleshooting-guide.md`。 首次启动前安装前端依赖: ```bash cd iam-ui npm install ``` 启动前端: ```bash scripts/start-frontend.sh ``` 默认前端地址为 `http://localhost:38080`,Vite 会代理 `/api`、`/oauth2`、`/userinfo` 和 `/.well-known` 到后端 `http://localhost:28080`。默认本地 OAuth2 callback 地址为: ```text http://localhost:38080/login/oauth2/code/iam ``` 前端 OAuth2 callback 会按当前门户访问地址自动推导;如需覆盖本地代理、Issuer 或示例子系统地址,可在 `iam-ui/.env` 配置: ```bash VITE_IAM_BACKEND_ORIGIN=http://localhost:28080 VITE_IAM_ISSUER_ORIGIN=http://localhost:28080 VITE_IAM_CLIENT_ORIGIN=http://localhost:39080 ``` 推荐本地联调启动顺序: ```bash # 0. 如需使用项目内 MySQL / Redis,先启动本地基础服务 scripts/start-dev-services.sh # 1. 启动 IAM 后端,默认 http://localhost:28080 scripts/start-backend.sh # 2. 启动门户前端,默认 http://localhost:38080 scripts/start-frontend.sh # 3. 如需验证保密客户端,单独启动示例子系统,默认 http://localhost:39080 IAM_CLIENT_SECRET='复制管理端明文 Secret' scripts/start-client-demo.sh ``` 服务启动后,可先执行统一验收入口的只读端点检查,快速确认后端 discovery、门户前端和可选示例子系统都可访问: ```bash scripts/accept-oauth2-flow.sh --skip-backend --skip-frontend-smoke --with-running-services scripts/accept-oauth2-flow.sh --skip-backend --skip-frontend-smoke --with-client-demo ``` `start-client-demo.sh` 不会把 Client Secret 写入配置文件或持久化保存。推荐启动时通过 `IAM_CLIENT_SECRET` 显式传入当前要验证的子系统 Secret。公共 PKCE 客户端使用门户前端 callback 验证即可;生成过 Client Secret 的保密客户端应使用 `iam-client-demo` 这类服务端 OAuth2 Client 验证。 生产门户使用同源部署时,前端会从浏览器当前 Origin 动态生成 Issuer 和门户回调地址。正式域名 `https://iam.lxcloud.ltd` 对应公共客户端回调 `https://iam.lxcloud.ltd/login/oauth2/code/iam`、退出回调 `https://iam.lxcloud.ltd/login?logout=iam`;V11 migration 会追加这两个地址,不会删除现有本地联调地址。 管理端应用详情页提供 **子系统接入配置** 预览,会展示 Client ID、Issuer、首个回调地址和授权范围,并可一键复制 Spring Boot OAuth2 Client YAML 或 ENV 配置片段。接入真实子系统时,建议优先以这里复制的配置为准,再按环境补充 `IAM_CLIENT_SECRET`。 ### 本地 SSO 验收清单 完成本地启动后,优先通过管理首页的 **SSO 联调验收** 面板确认链路,不建议每次都手工拼授权地址: 1. 执行 `scripts/accept-oauth2-flow.sh --skip-backend --skip-frontend-smoke --with-running-services`,确认后端 discovery、门户前端均可达;如已启动示例子系统,再执行 `scripts/accept-oauth2-flow.sh --skip-backend --skip-frontend-smoke --with-client-demo`。 2. 访问 `http://localhost:38080`,使用初始化管理员账号登录门户。 3. 在门户首页点击一个可访问应用的“进入应用”,确认浏览器跳转到 IAM 授权端并完成回调。 4. 回到管理首页,按 **SSO 联调验收** 面板依次检查: - `查登录`:确认今日 `AUTH_LOGIN` 审计记录存在。 - `查启动`:确认今日 `PORTAL_APP_LAUNCH` 审计记录存在。 - `查授权`:确认 OAuth2 授权记录、Token 状态和授权范围正常。 - `查应用`:确认应用 Client、回调地址、授权类型和同步状态正常。 5. 如果点击门户应用失败,优先从管理首页风险项、审计分布里的失败原因或审计详情进入诊断,再回看应用授权角色、回调地址和 Client Secret 使用方式。 6. 公共 PKCE 客户端使用门户前端 callback 验证;带 Client Secret 的保密客户端使用 `iam-client-demo` 验证。 完成一次验收后,建议保留以下记录,后续排查子系统接入问题时可以直接对照: - 管理首页 **SSO 联调验收** 四个检查项的当前状态。 - 授权记录中的应用、授权主体、Scope 和 Token 状态。 - 审计日志中的 `AUTH_LOGIN`、`PORTAL_APP_LAUNCH` 以及失败原因。 - 应用详情里的 Client ID、Issuer、回调地址、授权类型、Scope 和同步状态。 ## 数据库与 Flyway 后端基础配置位于 `application.yml`,`local` profile 会加载 `application-local.yml` 中的本地联调默认数据库和 Redis 配置;如需切到本机服务,可以通过 `DB_URL`、`DB_USERNAME`、`DB_PASSWORD`、`REDIS_HOST`、`REDIS_PASSWORD` 等环境变量覆盖启动脚本参数。 项目内置 `deploy/docker-compose.yml` 可启动本地 MySQL 8.4 和 Redis 7: ```bash scripts/start-dev-services.sh # 启动基础服务 scripts/start-dev-services.sh status # 查看容器状态 scripts/start-dev-services.sh logs # 查看基础服务日志 scripts/start-dev-services.sh down # 停止基础服务 ``` `local` profile 保留本地联调默认值,并支持通过 `.env` 或环境变量覆盖数据库、Redis、Issuer 和 Token Secret。`prod` profile 不内置数据库、Redis、Issuer 或 Token Secret 默认值,必须由环境变量、配置中心或部署平台注入: ```bash SPRING_PROFILES_ACTIVE=prod \ DB_URL='jdbc:mysql://mysql.example.com:3306/lxcloud_iam?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai' \ DB_USERNAME='lxcloud_iam' \ DB_PASSWORD='生产数据库密码' \ REDIS_HOST='redis.example.com' \ REDIS_PASSWORD='生产 Redis 密码' \ IAM_ISSUER='https://iam.lxcloud.ltd' \ IAM_LOGIN_MODE='portal' \ IAM_PORTAL_LOGIN_URL='https://iam.lxcloud.ltd/login' \ IAM_SMS_LOGIN_ENABLED='false' \ IAM_SMS_LOGIN_LOGGING_SENDER_ENABLED='false' \ IAM_TOKEN_SECRET='至少 32 字节的生产随机密钥' \ IAM_JWK_SET_FILE='/var/www/lxcloud-iam/secrets/iam-jwk-set.json' \ IAM_JWK_ACTIVE_KEY_ID='当前活动 RSA 密钥 kid' \ scripts/start-backend.sh ``` 生产启动前可先执行只读环境变量预检,提前发现缺失配置、非 `prod` profile、过短 Token Secret、仍使用本地默认密钥或未配置稳定 JWK 签名密钥等问题: ```bash SPRING_PROFILES_ACTIVE=prod \ DB_URL='jdbc:mysql://mysql.example.com:3306/lxcloud_iam?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai' \ DB_USERNAME='lxcloud_iam' \ DB_PASSWORD='生产数据库密码' \ REDIS_HOST='redis.example.com' \ REDIS_PASSWORD='生产 Redis 密码' \ IAM_ISSUER='https://iam.lxcloud.ltd' \ IAM_LOGIN_MODE='portal' \ IAM_PORTAL_LOGIN_URL='https://iam.lxcloud.ltd/login' \ IAM_SMS_LOGIN_ENABLED='false' \ IAM_SMS_LOGIN_LOGGING_SENDER_ENABLED='false' \ IAM_TOKEN_SECRET='至少 32 字节的生产随机密钥' \ IAM_JWK_SET_FILE='/var/www/lxcloud-iam/secrets/iam-jwk-set.json' \ IAM_JWK_ACTIVE_KEY_ID='当前活动 RSA 密钥 kid' \ scripts/check-prod-env.sh ``` 全新数据库首次启动前,可执行一次管理员初始化;普通启动不要打开初始化开关: ```bash IAM_INIT_ADMIN_PASSWORD='首次管理员临时密码' \ scripts/init-admin.sh \ --env-file /var/www/lxcloud-iam/secrets/lxcloud-iam.env \ --jar /var/www/lxcloud-iam/api/lxcloud-iam.jar ``` 初始化会检查数据库连通性、Issuer、门户登录地址、Token Secret、JWK Set、根组织和管理员角色,并创建管理员或在显式设置 `IAM_INIT_ADMIN_RESET_PASSWORD=true` 时重置管理员密码。 生产服务启动后,可再执行只读 HTTP 验收,确认公网入口、Discovery、JWK、授权服务登录页 `/iam-login` 和门户登录页 `/login` 都按预期路由: ```bash scripts/check-prod-deployment.sh --env-file /var/www/lxcloud-iam/secrets/lxcloud-iam.env ``` 生产发布、迁移或升级前,建议先导出一次 MySQL 逻辑备份: ```bash scripts/backup-prod-mysql.sh --env-file /var/www/lxcloud-iam/secrets/lxcloud-iam.env ``` 备份文件默认写入 `backups/mysql`,该目录不会提交到 Git。恢复生产数据、执行 Flyway repair 或处理 checksum mismatch 前,先按 `docs/production-readiness-checklist.md` 完成备份、审批和影响确认。 `IAM_JWK_SET_FILE` 指向 OAuth2 / OIDC RSA 签名密钥文件。生产环境必须使用固定的 JWK Set JSON,并在所有副本和重启之间保持一致;文件建议挂载到 `/run/secrets/iam-jwk-set.json`,权限限制为仅部署用户或容器可读。不要依赖本地开发默认的启动时随机密钥,否则服务重启后旧 ID Token / Access Token 的 JWK 验签会失效。单把私钥时可自动识别活动密钥;配置多把私钥进行轮换时,必须通过 `IAM_JWK_ACTIVE_KEY_ID` 指定新 Token 使用的活动 `kid`。旧密钥可只保留公钥,继续用于过渡期验签和 JWK 发布。 如需确认当前 Shell 环境没有依赖本地 `.env`,可显式跳过 `.env` 加载: ```bash LOAD_DOTENV=false scripts/check-prod-env.sh ``` Flyway 迁移脚本一旦在本地或共享开发库执行过,原则上不要再修改历史 `V*.sql` 文件;已有结构或种子数据调整应新增下一版迁移,例如 `V7__xxx.sql`。如果开发库中已经执行过某个版本,但本地文件被确认需要接受当前内容,才执行 repair 更新 `flyway_schema_history` 校验值。 当前项目未配置 Flyway Maven 插件,推荐用 Spring Boot 启动时自动迁移,repair 可临时使用 Flyway CLI 或在确认数据库参数后执行一次性修复工具。若只是在本地开发库处理历史 checksum mismatch,可以用 Docker 临时运行 Flyway CLI: ```bash docker run --rm \ -v "$PWD/iam-server/src/main/resources/db/migration:/flyway/sql:ro" \ flyway/flyway:10 \ -url='jdbc:mysql://172.16.101.122:3306/lxcloud_iam?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai' \ -user='root' \ -password='123456' \ repair ``` repair 只会修复 Flyway 历史表中的校验记录,不会重放 SQL,也不会修正业务数据。执行前应确认数据库就是当前要修复的开发库。 如果只是调整初始化数据或演示回调地址,优先新增下一版迁移;只有在明确清理了开发库、或确认当前 SQL 与库内历史记录应该重新对齐时,才把 repair 作为一次性修复动作。 ## 本地验证 默认单元和服务层测试不依赖真实数据库连接,适合在没有启动 MySQL 的情况下快速验证代码行为。需要连接局域网 MySQL 的启动联调,使用 `scripts/start-backend.sh`;需要验证真实 Flyway 迁移执行或 OAuth2 数据链路时,再用实际数据库启动后端进行浏览器联调。 ```bash scripts/verify-local.sh ``` 默认会依次执行 Flyway migration 文件检查、部署模板检查、后端测试、示例子系统测试、前端公开门户 smoke、OAuth2 回调 smoke、后台页面 smoke、高风险后台操作 smoke 和前端构建。只验证其中一侧时可使用: ```bash scripts/verify-local.sh --backend-only scripts/verify-local.sh --frontend-only ``` 已启动后端、门户前端和 `iam-client-demo` 后,可以追加真实浏览器回归。首次运行需要安装 Playwright Chromium: ```bash cd iam-ui npx playwright install chromium cd .. scripts/verify-local.sh --frontend-only --browser-e2e ``` OAuth2 授权码链路也提供了固定验收入口,详见 [OAuth2 授权码链路验收清单](docs/oauth2-authorization-code-acceptance.md): ```bash scripts/accept-oauth2-flow.sh scripts/accept-oauth2-flow.sh --with-client-demo --browser-e2e ``` 浏览器回归使用 Playwright 串行验证 OAuth2 链路和后台高风险入口: - 公共客户端 `subsystem-a`:门户登录、PKCE 授权、`/oauth2/token` 换码、`/userinfo` 读取,以及刷新回调页不重复提交一次性授权码。 - 保密客户端:从示例子系统发起登录、服务端携带目标应用的 Client Secret 换码、身份与 Token 页面展示,以及 `/connect/logout` 返回门户。默认本地种子应用为 `subsystem-b`,可通过 `IAM_CLIENT_ID` 切换。 - 后台高风险入口:登录管理端后检查应用启用 / 禁用确认、客户端密钥重新生成确认、用户重置密码 / 分配角色弹框和 OAuth2 授权撤销确认;测试只打开确认层并取消,不执行写操作。 默认使用本地地址和初始化管理员账号。联调环境或预生产环境可以通过环境变量覆盖,测试不会读取、打印或修改 `IAM_CLIENT_SECRET`: ```bash IAM_E2E_PORTAL_URL='https://iam.lxcloud.ltd' \ IAM_E2E_ISSUER_URL='https://iam.lxcloud.ltd' \ IAM_E2E_CLIENT_DEMO_URL='https://subsystem-b.example.com' \ IAM_E2E_USERNAME='专用验收账号' \ IAM_E2E_PASSWORD='专用验收密码' \ scripts/verify-local.sh --frontend-only --browser-e2e ``` 如果验收账号已启用认证器双因素认证,建议额外提供该账号的 TOTP 手动密钥,让 Playwright 每次登录时自动生成当前动态码: ```bash IAM_E2E_TOTP_SECRET='验收账号的 Base32 手动密钥' \ scripts/verify-local.sh --frontend-only --browser-e2e ``` 也可以临时使用 `IAM_E2E_MFA_CODE` 提供当前动态码或一组未使用的恢复码;动态码会过期,恢复码通常只能使用一次。 如果只需要执行浏览器链路,可在 `iam-ui` 目录按需运行: ```bash npm run test:oauth-e2e npm run test:admin-e2e npm run test:browser-e2e ``` OAuth2 浏览器回归要求三项服务已启动,并已为当前 `IAM_CLIENT_ID` 对应应用配置有效 Client Secret;后台高风险入口回归要求后端和门户前端已启动。普通 `scripts/verify-local.sh` 仍不会启动服务或依赖真实数据库。 也可以单独执行 Flyway migration 文件级检查: ```bash scripts/check-flyway-migrations.sh ``` 该检查只验证 migration 目录、文件命名、版本唯一性、版本连续性和空文件问题,不连接 MySQL,也不替代真实环境中的 Flyway migrate。 推荐 CI 或提交前最小验证命令: ```bash scripts/verify-local.sh git diff --check ``` 如果 CI 环境拆分前后端任务,可以分别执行: ```bash scripts/verify-local.sh --backend-only scripts/verify-local.sh --frontend-only ``` 发布前可额外验证宿主机发布包: ```bash scripts/package-host-release.sh scripts/dry-run-host-release.sh --archive build/lxcloud-iam-host.tar.gz ``` 仓库已提供 GitHub Actions 基线配置 `.github/workflows/ci.yml`,默认拆分 Java 测试、前端检查和发布包 dry-run 三条作业;其他 CI 平台可复用同一组脚本命令。 生产发布后,按 `docs/production-sso-smoke-checklist.md` 完成一次真实浏览器 SSO 验收。 数据库级 Flyway 验证应放在有临时 MySQL、预生产库或生产备份恢复库的环境中执行。最小验收标准是:应用启动触发 migrate 成功、`flyway_schema_history` 没有失败记录、没有 checksum mismatch、后端健康检查和 OAuth2 discovery 可访问。不要在普通单元测试里默认连接真实数据库,避免本地验证被网络或数据库状态阻断。 已启动本地后端和前端时,可执行统一验收入口里的本地 SSO 只读端点检查: ```bash scripts/accept-oauth2-flow.sh --skip-backend --skip-frontend-smoke --with-running-services ``` ### 常见联调问题 - **端口占用**:后端默认 `28080`,门户前端默认 `38080`,示例子系统默认 `39080`。如端口冲突,通过 `PORT=端口号 scripts/start-*.sh` 覆盖。 - **Maven/JDK 不一致**:后端和 demo 都要求 JDK 21。脚本会优先使用 `/usr/lib/jvm/java-21-openjdk-amd64`,也可以显式设置 `JAVA_HOME`。 - **依赖仓库隔离**:默认使用工作区内 `.m2/repository`;如确认要复用本机仓库,设置 `MAVEN_REPO=/home/lxy/.m2/repository`。 - **Flyway migration 文件检查失败**:按提示修正 `iam-server/src/main/resources/db/migration` 下的文件名、版本号或空文件问题。该检查不连接数据库,checksum mismatch 仍以实际数据库启动或 Flyway CLI 输出为准。 - **Flyway checksum mismatch**:不要直接反复修改已执行过的历史迁移。确认当前 SQL 就是开发库要接受的版本后,再执行 `repair` 更新校验记录。 - **`invalid_client`**:通常是 Client ID、Client Secret、回调地址、客户端类型不匹配。公共 PKCE 客户端不要携带 Client Secret;保密客户端必须通过服务端示例子系统携带 Client Secret 换 Token。 - **回调地址不匹配**:所有本地回调建议统一使用 `localhost`,并确保应用配置中登记了实际访问地址,例如门户前端 `http://localhost:38080/login/oauth2/code/iam` 或示例子系统 `http://localhost:39080/login/oauth2/code/iam`。 - **前端显示网络异常或请求超时**:先确认 `scripts/start-backend.sh` 已启动成功,再确认前端代理目标为 `http://localhost:28080`。 启动后可使用初始化管理员账号登录: ```bash curl -sS http://localhost:28080/api/auth/password-policy curl -sS http://localhost:28080/api/auth/login-security-policy curl -sS http://localhost:28080/api/auth/security-policies curl -sS -X POST http://localhost:28080/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"Admin@123456"}' ``` 登录响应会返回 `accessToken` 和 `refreshToken`。后续 `/api/**` 管理端和门户接口使用登录/刷新接口返回的 `accessToken` 作为 Bearer Token,该 Token 必须包含 `token_type=access`;`refreshToken`、OIDC `id_token` 或缺少 `token_type=access` 的 JWT 不能作为 API Bearer Token 使用。access token 过期后可用 refresh token 换取新令牌。 门户和后台登录接口签发的 refresh token 会携带 `refresh_family_id` 并启用轮换:每次刷新成功后旧 refresh token 会立即失效;如果旧 refresh token 被再次使用,IAM 会吊销同一登录会话 family 下的 refresh token。旧格式、缺少 `refresh_family_id` 的 refresh token 会被视为无效令牌: ```bash curl -sS -X POST http://localhost:28080/api/auth/refresh \ -H 'Content-Type: application/json' \ -d "{\"refreshToken\":\"${REFRESH_TOKEN}\"}" curl -sS -X POST http://localhost:28080/api/auth/revoke \ -H 'Content-Type: application/json' \ -d "{\"refreshToken\":\"${REFRESH_TOKEN}\"}" curl -sS http://localhost:28080/api/auth/me \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/auth/token-info \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/auth/logout \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d "{\"refreshToken\":\"${REFRESH_TOKEN}\"}" # 手工验证标准 OIDC 退出端点。真实子系统应先清理本地会话, # 再携带 id_token_hint 与已登记的 post_logout_redirect_uri 跳转到该端点。 # 只携带 post_logout_redirect_uri 会返回 400,因为 RP-Initiated Logout 要求 id_token_hint。 curl -I "http://localhost:28080/connect/logout?id_token_hint=${OIDC_ID_TOKEN}&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A38080%2Flogin%3Flogout%3Diam" curl -sS http://localhost:28080/api/admin/users \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/users/1000000000000000002 \ -H "Authorization: Bearer ${ACCESS_TOKEN}" ``` ## OAuth2 / OIDC 验证 服务已启用 Spring Authorization Server 基础链路,并会在启动时把启用状态且 OAuth2 配置有效的 `iam_app` 同步到标准 `oauth2_registered_client` 表。当前初始化数据里的子系统客户端为公共客户端,适合使用授权码 + PKCE 流程接入。 常用发现端点: ```bash curl -sS http://localhost:28080/.well-known/oauth-authorization-server curl -sS http://localhost:28080/.well-known/openid-configuration curl -sS http://localhost:28080/oauth2/jwks ``` 初始化客户端示例: - `client_id`:`subsystem-a` - 生产/示例 `redirect_uri`:`https://subsystem-a.example.com/login/oauth2/code/iam` - 本地联调 `redirect_uri`:`http://localhost:38080/login/oauth2/code/iam` - `scope`:`openid profile email` - `grant_type`:`authorization_code` - `code_challenge_method`:`S256` 本地最小授权码 + PKCE callback 验证流程: ```bash CODE_VERIFIER='lxcloud-smoke-pkce-verifier' CODE_CHALLENGE="$(printf '%s' "${CODE_VERIFIER}" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')" REDIRECT_URI='http://localhost:38080/login/oauth2/code/iam' printf 'http://localhost:28080/oauth2/authorize?response_type=code&client_id=subsystem-a&scope=openid%%20profile%%20email&redirect_uri=%s&state=demo&code_challenge=%s&code_challenge_method=S256\n' "${REDIRECT_URI}" "${CODE_CHALLENGE}" ``` 现在也可以直接用前端门户完成本地 callback 验证:启动后端与前端,访问 `http://localhost:38080`,使用初始化管理员账号登录门户,点击子系统应用的“进入应用”,前端会保存 `state` 与 `codeVerifier`,确认浏览器 SSO 会话,通过同源 `/oauth2/authorize` 代理跳转授权端点,并在 `/login/oauth2/code/iam` 回调页完成 token exchange 与 `/userinfo` 读取。 若应用已经生成 Client Secret,则属于保密客户端,不能再用门户前端 callback 直接换取 Token。此时应使用服务端 OAuth2 Client 完成回调。项目内提供了 `iam-client-demo` 作为可配置的示例子系统,用于验证任意已登记保密客户端;本地默认 Client ID 是初始化数据里的 `subsystem-b`,可通过 `IAM_CLIENT_ID` 覆盖: 管理端应用详情页的 **子系统接入配置** 可直接复制与当前应用一致的 Spring Boot 配置。页面生成逻辑会使用当前浏览器访问源推导 Issuer,例如本地门户 `http://localhost:38080` 会生成 `http://localhost:28080`;回调地址取当前应用已登记的首个回调地址,授权范围取应用 OAuth2 配置。生成内容示例: ```yaml spring: security: oauth2: client: provider: iam: issuer-uri: http://localhost:28080 registration: iam: client-id: subsystem-b client-secret: ${IAM_CLIENT_SECRET} authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" scope: openid,profile,email ``` 同一区域也可以复制 ENV 片段: ```bash IAM_ISSUER_URI=http://localhost:28080 IAM_CLIENT_ID=subsystem-b IAM_CLIENT_SECRET=请填写重新生成后的客户端密钥 IAM_REDIRECT_URI=http://localhost:39080/login/oauth2/code/iam IAM_SCOPE=openid,profile,email ``` 应用详情页同一区域会展示标准 OAuth2 / OIDC 端点,子系统接入时优先使用 `issuer-uri` 自动发现;只有排查或手工联调时才需要逐个复制端点: | 端点 | 本地地址 | 用途 | | --- | --- | --- | | Discovery 配置 | `http://localhost:28080/.well-known/openid-configuration` | Spring Security 等客户端自动发现授权、Token、JWK 等端点 | | 授权端点 | `http://localhost:28080/oauth2/authorize` | 浏览器发起授权码登录 | | Token 端点 | `http://localhost:28080/oauth2/token` | 使用授权码或 refresh token 换取 Token | | 用户信息端点 | `http://localhost:28080/userinfo` | 使用 OIDC Access Token 读取用户声明 | | 退出端点 | `http://localhost:28080/connect/logout` | OIDC 标准退出,清理 IAM 浏览器会话 | | Token 撤销端点 | `http://localhost:28080/oauth2/revoke` | 子系统撤销 access token 或 refresh token | | Token 内省端点 | `http://localhost:28080/oauth2/introspect` | 受信任服务检查 opaque token 或授权服务记录 | | JWK 公钥端点 | `http://localhost:28080/oauth2/jwks` | 资源服务获取 JWT 验签公钥 | ```bash # 1. 在管理端给目标应用重新生成 Client Secret,并登记示例子系统本地回调: # http://localhost:39080/login/oauth2/code/iam # 2. 启动 IAM 后端 scripts/start-backend.sh # 3. 使用目标应用的 Client ID 和明文 Client Secret 启动示例子系统 IAM_CLIENT_ID=subsystem-b \ IAM_CLIENT_SECRET='复制管理端明文 Secret' scripts/start-client-demo.sh # 4. 浏览器访问示例子系统 http://localhost:39080 ``` 示例子系统会跳转到 IAM 授权端,登录完成后回调 `http://localhost:39080/login/oauth2/code/iam`,由示例子系统服务端携带 Client Secret 换取 Token 并展示 OIDC claims。这个链路用于验证真实后端子系统的 SSO 接入;门户前端 callback 仍用于无 Client Secret 的公共 PKCE 客户端 smoke test。若启动时报 `IAM_CLIENT_SECRET is required`,先在管理端对目标应用执行“重新生成 Client Secret”,复制弹框里的明文 Secret 后重新启动示例子系统。 示例子系统里的“退出 IAM 并返回门户”使用 OIDC 标准退出链路,默认跳转到 `http://localhost:28080/connect/logout`,并携带 `id_token_hint` 与 `post_logout_redirect_uri=http://localhost:38080/login?logout=iam`。目标应用需要精确登记该 `post_logout_redirect_uri`;本地初始化迁移已为默认示例应用 `subsystem-b` 增加该门户登录页回跳地址,切换其他应用时需要在管理端补齐并同步 OAuth2 Client。 如需手工验证,也可以在另一个终端临时监听本地 callback,然后用浏览器打开上一步输出的授权地址。默认 `IAM_LOGIN_MODE=backend` 时,未登录会先跳转到后端授权服务网页登录页 `/iam-login`;如切换为 `IAM_LOGIN_MODE=portal`,本地应跳转到 `http://localhost:38080/login?redirect=/oauth2/authorize...`,门户登录成功后继续原始授权请求。登录同意后 callback 请求里会带上 `code` 和 `state`: ```bash nc -l localhost 38080 ``` 拿到授权码后换取 Token: 授权码只能使用一次,刷新回调页、重复提交同一个 `code`,或使用与发起授权时不一致的 `redirect_uri` / `code_verifier` 都会导致换码失败;失败后应从门户重新点击进入应用,生成新的授权请求。 ```bash AUTH_CODE='浏览器回调里的 code' curl -sS -X POST http://localhost:28080/oauth2/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d "grant_type=authorization_code" \ -d "client_id=subsystem-a" \ -d "redirect_uri=${REDIRECT_URI}" \ -d "code=${AUTH_CODE}" \ -d "code_verifier=${CODE_VERIFIER}" ``` 通过标准 OAuth2 授权码流程换取的 OAuth2 Access Token 可调用 OIDC UserInfo 端点读取当前用户声明: ```bash curl -sS http://localhost:28080/userinfo \ -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" ``` UserInfo 会返回标准声明和 IAM 扩展声明,例如 `sub`、`preferred_username`、`name`、`email`、`roles`、`permissions`、`lxcloud_user_id`、`lxcloud_org_id`、`lxcloud_org_code`。管理端创建或更新应用后,会即时同步启用状态且配置有效的应用到标准 OAuth2 客户端配置;禁用应用会移除对应客户端。若启用应用缺少授权类型、授权范围、授权码模式回调地址,或 Token TTL 非法,启动自动同步会清理旧客户端并跳过保存,手动同步会返回 `40003` 和具体校验原因。 管理端列表接口默认返回分页结构: ```json { "records": [], "total": 0, "page": 1, "size": 20, "pages": 0 } ``` 通用分页参数为 `page`、`size`、`keyword`,`size` 最大 200。常用筛选参数如下: ```bash curl -sS 'http://localhost:28080/api/admin/users?page=1&size=20&keyword=admin&status=ENABLED' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/orgs?page=1&size=20&keyword=root&status=ENABLED' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/orgs/1000000000000000001 \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/orgs/tree?status=ENABLED' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/orgs/filter-options \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/orgs/1000000000000000001/usage \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/orgs/1000000000000000001/detail-overview \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/orgs/1000000000000000001/audit-timeline?size=10' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/roles?page=1&size=20&keyword=admin&status=ENABLED' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/roles/1000000000000000101 \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/roles/1000000000000000101/permission-grant \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/roles/1000000000000000101/authorization-overview \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/roles/1000000000000000101/usage \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/roles/1000000000000000101/audit-timeline?size=10' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/permissions?page=1&size=20&permissionType=MENU&status=ENABLED' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/permissions/1000000000000000201 \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/permissions/1000000000000000201/usage \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/permissions/1000000000000000201/role-grant \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/permissions/1000000000000000201/audit-timeline?size=10' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X PUT http://localhost:28080/api/admin/permissions/1000000000000000201/roles \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"roleIds":[1000000000000000101]}' curl -sS 'http://localhost:28080/api/admin/permissions/tree?status=ENABLED' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/apps?page=1&size=20&keyword=subsystem&status=ENABLED' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/apps/1000000000000001001 \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/apps/filter-options \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/apps/1000000000000001001/oauth2-client \ -H "Authorization: Bearer ${ACCESS_TOKEN}" # 预览标准 OAuth2 client 同步配置、PKCE/密钥模式、授权类型、回调地址、scope 和校验结果。 curl -sS http://localhost:28080/api/admin/apps/1000000000000001001/usage \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/apps/1000000000000001001/authorization-overview \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/apps/1000000000000001001/audit-timeline?size=10' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/apps/1000000000000001001/client-secret/rotate \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/apps/1000000000000001001/oauth2-client/sync \ -H "Authorization: Bearer ${ACCESS_TOKEN}" # 手动同步会先校验 OAuth2 client 配置;启用应用配置无效时返回 40003,不写入半成品客户端。 curl -sS http://localhost:28080/api/admin/dashboard/summary \ -H "Authorization: Bearer ${ACCESS_TOKEN}" # 返回用户、角色、应用、今日审计统计,以及最近 7 日登录成功/失败趋势。 # 今日审计统计包含登录成功/失败、刷新成功、吊销成功、门户应用启动成功/失败。 curl -sS 'http://localhost:28080/api/admin/dashboard/recent-audits?size=10' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/dashboard/login-trend?days=7' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/dashboard/resource-overview \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/dashboard/audit-distribution \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/audit-logs?page=1&size=20&actorUsername=admin&actorUserId=1000000000000000002&targetType=USER&targetId=1000000000000000002&clientId=subsystem-a&ipAddress=127.0.0.1&result=SUCCESS&failureReason=BAD_CREDENTIALS_LOCKED&startTime=2026-07-01T00:00:00&endTime=2026-07-31T23:59:59' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/audit-logs/stats?actorUsername=admin&targetType=USER&result=SUCCESS&failureReason=BAD_CREDENTIALS_LOCKED&startTime=2026-07-01T00:00:00&endTime=2026-07-31T23:59:59' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/audit-logs/export?clientId=subsystem-a&result=FAILURE&startTime=2026-07-01T00:00:00&endTime=2026-07-31T23:59:59' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -o lxcloud-iam-audit-logs.csv curl -sS 'http://localhost:28080/api/admin/audit-logs/retention-preview?retentionDays=180' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" # 审计统计会返回 total/success/failure/login/refresh/revoke/logout/portalAppLaunch/adminAction 等计数。 # PORTAL_APP_LAUNCH 用于记录门户启动应用行为,属于用户侧门户行为,不计入 adminActionCount。 # 审计日志列表、统计和导出接口支持按 traceId 精确过滤,支持按 failureReason 模糊过滤,便于串联定位登录锁定、回调地址未登记、退出通知失败等问题。 # 管理首页审计分布也会聚合失败原因,点击某个失败原因会跳转到审计日志并自动带上 result=FAILURE 与 failureReason 筛选。 # 审计保留预检接口只返回保留天数、截止时间和将受影响的审计数量;审批通过后可人工归档复制到归档表,但不会删除原始审计数据。 # 默认保留周期为 iam.audit.retention-days=180,可预检范围限制为 30 到 3650 天。 # 如需自动生成待审批计划,可显式开启 IAM_AUDIT_RETENTION_AUTO_PLAN_ENABLED=true;该任务只生成 PENDING_APPROVAL 计划,不会自动审批、归档或删除。 # 管理首页“审计保留待处理”会聚合待审批、待归档和已归档待清理预检计划;提醒是否展示受风险事件配置中的阈值和启停控制。 # 审计日志页保留预检弹框可查看计划详情和归档批次。 # 已归档计划可调用 cleanup-readiness 做清理前只读预检;响应会明确返回删除执行未开放、执行要求和下一步提示,当前版本仍不提供删除执行接口。 curl -sS http://localhost:28080/api/admin/audit-logs/filter-options \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/audit-logs/9001 \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/audit-logs?traceId=trace-logout-001&result=FAILURE' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/oauth2-authorizations?principalName=admin&clientId=subsystem-a&tokenStatus=ACTIVE' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/oauth2-authorizations/export?principalName=admin&clientId=subsystem-a&tokenStatus=ACTIVE' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -o lxcloud-iam-oauth2-authorizations.csv curl -sS 'http://localhost:28080/api/admin/logout-notifications?clientId=subsystem-a&traceId=trace-logout-001' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/logout-notifications/9001/retry \ -H "Authorization: Bearer ${ACCESS_TOKEN}" ``` 常用用户管理接口示例: ```bash curl -sS -X POST http://localhost:28080/api/admin/users \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"username":"demo","password":"Demo@123456","displayName":"演示用户","email":"demo@lxcloud.ltd","status":"ENABLED"}' curl -sS http://localhost:28080/api/admin/users/filter-options \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X PUT http://localhost:28080/api/admin/users/1000000000000000002/roles \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"roleIds":[1000000000000000101]}' curl -sS http://localhost:28080/api/admin/users/1000000000000000002/role-grant \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/users/1000000000000000002/access-summary \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/admin/users/1000000000000000002/audit-timeline?size=10' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/admin/users/1000000000000000002/security \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X PATCH http://localhost:28080/api/admin/users/1000000000000000002/password \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"password":"New@123456"}' curl -sS -X POST http://localhost:28080/api/admin/users/1000000000000000002/disable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/users/1000000000000000002/enable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/users/1000000000000000002/unlock \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/orgs/1000000000000000001/disable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/orgs/1000000000000000001/enable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" ``` 常用配置管理接口示例: ```bash curl -sS -X POST http://localhost:28080/api/admin/roles \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"roleCode":"SUBSYSTEM_ADMIN","roleName":"子系统管理员","status":"ENABLED"}' curl -sS http://localhost:28080/api/admin/roles/filter-options \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/permissions \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"permissionCode":"iam:demo:read","permissionName":"演示读取","permissionType":"API","resourcePath":"/api/demo","status":"ENABLED"}' curl -sS http://localhost:28080/api/admin/permissions/filter-options \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X PUT http://localhost:28080/api/admin/roles/1000000000000000101/permissions \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"permissionIds":[1000000000000000201,1000000000000000202]}' curl -sS -X PUT http://localhost:28080/api/admin/roles/1000000000000000101/apps \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"appIds":[1000000000000001001]}' curl -sS http://localhost:28080/api/admin/roles/1000000000000000101/app-grant \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/roles/1000000000000000101/disable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/roles/1000000000000000101/enable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/permissions/1000000000000000201/disable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/permissions/1000000000000000201/enable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X PUT http://localhost:28080/api/admin/apps/1000000000000001001/roles \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"roleIds":[1000000000000000101]}' curl -sS http://localhost:28080/api/admin/apps/1000000000000001001/role-grant \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/apps/1000000000000001001/disable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/admin/apps/1000000000000001001/enable \ -H "Authorization: Bearer ${ACCESS_TOKEN}" ``` 门户个人中心与应用访问接口示例: ```bash curl -sS http://localhost:28080/api/portal/profile \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/portal/overview \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/portal/activity?size=10' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/portal/security \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS -X POST http://localhost:28080/api/portal/change-password \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"currentPassword":"Admin@123456","newPassword":"New@123456"}' curl -sS http://localhost:28080/api/portal/apps \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/portal/apps/1000000000000001001 \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS http://localhost:28080/api/portal/apps/1000000000000001001/access \ -H "Authorization: Bearer ${ACCESS_TOKEN}" curl -sS 'http://localhost:28080/api/portal/apps/1000000000000001001/launch?redirectUri=http://localhost:38080/login/oauth2/code/iam' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" ``` `/api/portal/apps/{appId}/launch` 可选传入 `redirectUri`,用于选择该应用已登记的回调地址;未传时默认使用应用配置里的第一个 `redirectUris`,传入未登记地址会被拒绝。接口会返回 `authorizeUrl`、`state`、`nonce`、`codeVerifier`、`codeChallenge`、`codeChallengeMethod` 等启动参数;前端跳转 `authorizeUrl` 后,需要保留 `state` 与 `codeVerifier`,分别用于回调防重放校验和后续授权码换取 Token。每次启动成功或失败都会写入 `PORTAL_APP_LAUNCH` 审计日志,并记录应用 ID、客户端 ID、IP、User-Agent、失败原因及回调地址摘要。