Captcha: get: operationId: fetchCaptcha summary: 登录图片验证码签发(安全需求4) description: 环境变量DMS_CAPTCHA_ENABLED(双版同名,1.40起缺省false)开启时返回captchaId与data:image/png;base64图片(132×44、4位字符剔除易混淆0O1I、120秒过期、一次性消费);关闭时仅返回enabled=false。登录页据enabled决定是否展示验证码输入;请求与响应均不缓存。 security: [] parameters: - $ref: '../components/parameters.yaml#/XRequestId' responses: '200': description: 验证码签发结果 content: application/json: schema: type: object required: [code, message, data, requestId] properties: code: {type: string, enum: [OK]} message: {type: string, enum: [success]} requestId: {type: string, format: uuid} data: type: object required: [enabled, passwordLoginDisabled] additionalProperties: false properties: enabled: {type: boolean, description: 验证码开关当前值} passwordLoginDisabled: {type: boolean, description: 禁用用户名密码登录开关回显(1.39安全管控,仅Java版);true时登录页隐藏账号密码表单,POST /auth/login一律403} captchaId: {type: string, description: 验证码标识,登录时原样携带;enabled=true时必返} image: {type: string, description: data:image/png;base64图片,img标签src直接使用;enabled=true时必返} '500': $ref: '../components/responses.yaml#/InternalError' ResetPasswordRequest: post: operationId: createResetPasswordRequest summary: 登录前提交重置密码申请(契约1.49) description: 登录界面(账号密码与单点登录独立密码界面)自助提交:username接受用户名或姓名(重名歧义按用户不存在处理,与登录口径一致);验证码开启(DMS_CAPTCHA_ENABLED)时请求体须含captchaId/captchaCode(与登录同一验证码,一次性消费)。按申请用户类型路由审批角色——首长(userType=COMMANDER)→DMS_RESET_PASSWORD_APPROVER_ROLE_COMMANDER(缺省「安全管理员」)、其他用户→DMS_RESET_PASSWORD_APPROVER_ROLE_NORMAL(缺省「系统管理员」),向该角色全部活跃挂载用户发PASSWORD_RESET待办通知(至少一人,否则400);内置admin拒绝申请(400);已有待审申请返回409 PASSWORD_RESET_DUPLICATE;用户不存在/停用返回400。审批人在通知页待办处理(AGREE/REJECT,先到先得,二次认证),通过后该用户password_hash/last_login_at置NULL(回首次登录态,门户验签进入SET_PASSWORD阶段重新设置独立密码)、auth_version+1旧会话全部失效;本地账号密码登录对已重置账号返回403「该账号密码已重置,请通过单点登录进入并重新设置密码」。 security: [] parameters: - $ref: '../components/parameters.yaml#/XRequestId' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: [username] properties: username: {type: string, description: 用户名或姓名。} captchaId: {type: string, description: 验证码标识(验证码开启时必填)。} captchaCode: {type: string, description: 验证码字符(验证码开启时必填)。} responses: '200': description: 申请已受理 content: application/json: schema: type: object required: [code, message, data, requestId] properties: code: {type: string, enum: [OK]} message: {type: string, enum: [success]} requestId: {type: string, format: uuid} data: type: object required: [requestId, notified] additionalProperties: false properties: requestId: {$ref: '#/StringId'} notified: {type: integer, minimum: 1, description: 收到待办通知的审批人数。} '400': {$ref: '../components/responses.yaml#/BadRequest'} '409': {$ref: '../components/responses.yaml#/Conflict'} '500': {$ref: '../components/responses.yaml#/InternalError'} Login: post: operationId: login summary: 用户登录 description: 验证码开启(DMS_CAPTCHA_ENABLED,1.40起缺省false)时请求体必须且只能包含五键(追加captchaId/captchaCode),验证码校验先于用户查询与锁定检查、一次性消费不计入登录失败锁定,错误返回400 INVALID_CAPTCHA;关闭时保持三键(username/password/keepSignedIn)。可选键roleId(契约1.46 多角色,数字字符串)与验证码开闭正交(关闭时三或四键、开启时五或六键)——密码/验证码/锁定/IP全过后挂载多个活跃启用角色且未带roleId时返回RoleSelectionData选择步骤(不签发令牌、不记登录成功),前端选定后携带roleId二次提交;roleId不在挂载列表内返回400 INVALID_ARGUMENT。会话锁定单角色:选定roleId写入JWT roleId claim(数字),切换角色需重新登录;单角色自动选中、无挂载走默认角色(claim为null)。密码即角色(契约1.53):先遍历挂载行独立密码哈希,命中即以该角色直接签发进入;未命中回退用户级密码验证,命中且恰一个挂载行未设独立密码时以该角色进入;挂载行全未设独立密码的多角色为存量过渡态,维持上述选择步骤(显式roleId路径保留兼容)。本地重设密(契约1.65):密码被重置审批清空(契约1.49)的用户本地登录不再返回403——验证码/锁定/IP守卫照常通过后,无论输入什么密码都返回SetupPasswordPhaseData设密载荷(与门户验签SET_PASSWORD同构、票据一次性5分钟有效),前端进入与单点登录首次设密同一界面重新设密;审计以reason=PASSWORD_RESET留痕。 security: [] parameters: - $ref: '../components/parameters.yaml#/XRequestId' requestBody: required: true content: application/json: schema: $ref: '../components/schemas.yaml#/LoginRequest' responses: '200': description: 登录成功(多角色未带roleId返回RoleSelectionData选择步骤;密码被重置审批清空返回SetupPasswordPhaseData设密步骤) content: application/json: schema: $ref: '../components/schemas.yaml#/LoginResponse' '400': description: 请求参数错误或验证码缺失/错误/过期(INVALID_CAPTCHA,安全需求4) content: application/json: schema: $ref: '../components/schemas.yaml#/ErrorResponse' '401': $ref: '../components/responses.yaml#/AuthenticationError' '403': $ref: '../components/responses.yaml#/Forbidden' '500': $ref: '../components/responses.yaml#/InternalError' Logout: post: operationId: logout summary: 注销并使当前用户旧Token失效 parameters: - $ref: '../components/parameters.yaml#/XRequestId' responses: '200': description: 注销成功 content: application/json: schema: $ref: '../components/schemas.yaml#/EmptySuccessResponse' '400': $ref: '../components/responses.yaml#/InvalidArgument' '401': $ref: '../components/responses.yaml#/AuthenticationError' '403': $ref: '../components/responses.yaml#/Forbidden' '500': $ref: '../components/responses.yaml#/InternalError' Reauth: post: operationId: reauth summary: 敏感操作二次认证(重输登录密码换取5分钟reauth令牌) description: 登录态下重输登录密码,签发type=reauth的短时令牌(绑定sub与authVersion,不可冒充访问令牌);不写审计、不计入登录失败锁定。 parameters: - $ref: '../components/parameters.yaml#/XRequestId' requestBody: required: true content: application/json: schema: type: object required: [password] additionalProperties: false properties: password: {type: string, format: password, minLength: 1} responses: '200': description: 二次认证成功 content: application/json: schema: type: object required: [code, message, data, requestId] properties: code: {type: string, enum: [OK]} message: {type: string, enum: [success]} requestId: {type: string, format: uuid} data: type: object required: [reauthToken, expiresIn] properties: reauthToken: {type: string, description: 二次认证令牌,经X-DMS-Reauth-Token请求头携带} expiresIn: {type: integer, enum: [300], description: 固定300秒(5分钟)} '400': $ref: '../components/responses.yaml#/InvalidArgument' '401': $ref: '../components/responses.yaml#/AuthenticationError' '500': $ref: '../components/responses.yaml#/InternalError' PortalVerify: post: operationId: verifyPortalLaunch summary: 统一门户跳转验签 description: 透传浏览器地址栏query串验签,按需落库建档并同步组织信息;返回跳转阶段,首次进入需设置独立密码。 security: [] parameters: - $ref: '../components/parameters.yaml#/XRequestId' requestBody: required: true content: application/json: schema: type: object required: [query] additionalProperties: false properties: query: {type: string, minLength: 1, description: 门户跳转链接的完整query串} responses: '200': description: 验签结果 content: application/json: schema: type: object required: [code, message, data, requestId] properties: code: {type: string} message: {type: string} requestId: {type: string} data: type: object required: [phase, username, realName] properties: phase: {type: string, enum: [LOGIN, SET_PASSWORD], description: SET_PASSWORD时携带一次性设密票据portalTicket及机构选择数据} username: {type: string} realName: {type: string} portalTicket: {type: string} suggestedOrganizationId: {type: string, nullable: true, description: 按对端机构名/默认组织推断的用户当前机构ID(可能为空),前端预选用} organizations: type: array description: 仅SET_PASSWORD阶段返回;本系统机构树与大系统可能不一致(用户按需同步、机构不自动同步),设密时由用户自选所在机构 items: type: object required: [id, orgCode, orgName, orgPath] additionalProperties: false properties: id: {type: string} orgCode: {type: string} orgName: {type: string} orgPath: {type: string} '400': $ref: '../components/responses.yaml#/InvalidArgument' '403': $ref: '../components/responses.yaml#/Forbidden' '500': $ref: '../components/responses.yaml#/InternalError' PortalProxyVerify: post: operationId: verifyProxySso summary: 本地单点登录代理探测(仅Java版提供) description: 令牌取自本机单点登录代理(REST直连:GET /middle/token/get.json取token,POST /auth/user/getUserBasicInfo以Bearer令牌换用户基本信息),经用户服务校验身份后按需同步建档;响应结构与门户跳转验签完全一致(双阶段LOGIN/SET_PASSWORD)。仅Java版后端提供本接口,Python版返回404、功能未启用返回400,前端探测失败静默回落账号密码登录。 security: [] parameters: - $ref: '../components/parameters.yaml#/XRequestId' responses: '200': description: 探测结果(结构与PortalVerify一致) content: application/json: schema: type: object required: [code, message, data, requestId] properties: code: {type: string} message: {type: string} requestId: {type: string} data: type: object required: [phase, username, realName] properties: phase: {type: string, enum: [LOGIN, SET_PASSWORD], description: SET_PASSWORD时携带一次性设密票据portalTicket及机构选择数据} username: {type: string} realName: {type: string} portalTicket: {type: string} suggestedOrganizationId: {type: string, nullable: true} roles: type: array description: 挂载角色简表(契约1.53 密码即角色);挂载≥2时返回,设密界面逐角色分配密码(恰一个使用用户密码)。 items: type: object required: [id, roleName] additionalProperties: false properties: id: {type: string} roleName: {type: string} organizations: type: array items: type: object required: [id, orgCode, orgName, orgPath] additionalProperties: false properties: id: {type: string} orgCode: {type: string} orgName: {type: string} orgPath: {type: string} '400': $ref: '../components/responses.yaml#/InvalidArgument' '401': $ref: '../components/responses.yaml#/AuthenticationError' '500': $ref: '../components/responses.yaml#/InternalError' PortalSetupPassword: post: operationId: setupPortalPassword summary: 门户用户首次设置独立密码并登录 description: 票据一次性且5分钟内有效;设置成功后签发访问令牌。可选携带organizationId同步设置用户所在机构(须为有效且启用的机构;缺省不改变)。多角色首登可携带rolePasswords逐角色分配密码(契约1.53),设密后以「使用用户密码」的角色直接签发进入。 security: [] parameters: - $ref: '../components/parameters.yaml#/XRequestId' requestBody: required: true content: application/json: schema: type: object required: [portalTicket, newPassword, keepSignedIn] additionalProperties: false properties: portalTicket: {type: string, minLength: 1} newPassword: {type: string, minLength: 1} keepSignedIn: {type: boolean} organizationId: {type: string, description: 用户自选的所在机构ID(verify响应organizations中的id);缺省不改变} rolePasswords: type: array description: 角色密码分配(契约1.53 密码即角色);挂载≥2角色时须提交——恰一个password=null(使用用户密码),其余独立密码互异且不等于newPassword,冲突400 PASSWORD_CONFLICT。 items: {$ref: '../components/schemas.yaml#/RolePasswordAssignment'} responses: '200': description: 设密并登录成功 content: application/json: schema: $ref: '../components/schemas.yaml#/LoginResponse' '400': $ref: '../components/responses.yaml#/InvalidArgument' '403': $ref: '../components/responses.yaml#/Forbidden' '500': $ref: '../components/responses.yaml#/InternalError'