ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot3+OAuth2 企业开放平台:客户端、Token、Scope、接口审计与第三方接入怎么落地

SpringBoot3+OAuth2 企业开放平台:客户端、Token、Scope、接口审计与第三方接入怎么落地 SpringBoot3OAuth2 企业开放平台客户端、Token、Scope、接口审计与第三方接入怎么落地文档地址https://ruoyioffice.com 文章底部获取源码和演示地址 17156169080获取产品咨询企业把订单、员工、库存或审批接口开放给第三方时最危险的做法不是“没有上 OAuth2”而是只有一个永不过期的固定 Token合作方是谁、能调什么、何时失效、调用失败后去哪里查全都说不清。真正可交付的开放平台至少要把客户端身份、授权范围、令牌生命周期和访问留痕连成一条治理链。▲ 第三方客户端先经过 Gateway再凭 OAuth2 客户端护照进入受控 API所有调用最终落到接口审计先说明真实边界当前工程交付的是 OAuth2/SSO 令牌底座不是完整的开发者开放平台。client_credentials已能换票但仓库内没有现成的 M2M 业务 APIScope 仅在用户读写示例接口落地通用 API 访问日志也不记录clientId。本文既拆解已有能力也说明从“能换票”走到“可交付开放平台”还必须补什么。引言开放 API 为什么不能只发一串密钥很多项目的第一版对接都很简单在配置文件里放一个api-key双方约定请求头然后开始联调。接口只有两个时看不出问题接入方增加以后四个缺口会同时出现身份混在一起多个合作方共用同一串密钥泄露后无法只停掉其中一家权限没有边界能查订单的 Token 顺手也能改员工接口权限靠口头约定生命周期不可控Token 永不过期人员离职或合同终止后仍然有效故障无法追溯只看到接口报错不知道哪个客户端、哪次请求、耗时多久。OAuth2 解决的是前三项中的“授权契约”访问日志解决第四项中的“调用事实”。二者不能只做一半。这篇文章不再重复浏览器 SSO 的授权码、同意页和单点登录而是以系统到系统的client_credentials目标链路为主线先验证现有代码已经做到哪里再把独立开放 API、显式 Scope 和客户端维度审计补进落地清单。一、现有 OAuth2 底座已经提供哪些控制面1. 客户端管理一套接入方一份契约OAuth2 Client 不是普通“应用名称”。它至少同时约束配置项决定什么配错后的风险clientId / secret谁在请求 Token多系统共用后无法单独撤销grantTypes允许怎样换票把 password 模式误开放给第三方scopes这张票最多申请什么Token 能力超过合作范围Token 有效期多久必须重新换票时间过长扩大泄露窗口redirectUris浏览器回调白名单授权码模式可能被劫持管理端把这些字段放在同一张客户端表单中。client_credentials本身不发生浏览器跳转但当前统一表单仍要求登记 Redirect URI它是客户端完整契约的一部分不代表客户端模式换票时会使用该地址。▲ 客户端编号、密钥、授权类型、Scope、Access/Refresh 有效期和回调地址集中配置业务代码不再硬编码合作方密钥2. Token 管理访问票必须可查、可撤、可过期第三方拿到的不是永久密钥而是一张有期限的访问票。管理端令牌台账可以按用户类型、客户端和过期时间检查当前会话也可以删除单张 Token。▲ Token 台账保留 Access、Refresh、客户端编号和过期时间删除访问票后无需等待自然过期截图中的令牌来自当前管理端登录用户编号为1。机器凭证模式在现有实现中会以userId0创建 Token不能把这张截图误解为一次真实合作方调用。3. Scope声明“这张票能做什么”完整开放平台的 Scope 应使用业务动作命名例如order.read读取订单order.write创建或修改订单employee.read读取允许开放的员工字段invoice.push推送发票结果。客户端配置的是“最大集合”换票请求只能申请它的子集。接口再通过PreAuthorize(ss.hasScope(order.read))声明所需 Scope才能完成最后一道校验。但当前仓库真正落地的只有user.read、user.write两个用户信息示例接口。order.read、invoice.push是本文给出的扩展示例不是现成业务 API。4. 接口日志回答“这张票实际做了什么”授权通过不等于调用成功。现有通用日志会保留 TraceId、请求地址、用户、请求耗时和结果码但没有clientId与 Scope 字段。出现超时、重复推送或参数争议时可以先按 TraceId 查调用事实要按合作方审计还需扩展客户端维度。▲ 这是全站通用 API 访问日志不是 OAuth2 专用审计它能按 TraceId 追请求但当前不能直接回答“是哪一个 Client 调用”二、从“能换票”走到“业务可用”还差什么第一步给合作方登记独立 Client不要用管理端默认 Client也不要给多个合作方共用 Client。每个接入系统单独配置编号、密钥、授权模式、Scope 和有效期。对机器接入建议只开启client_credentials。如果同一个应用还承担用户 SSO再单独评估是否开启authorization_code不要为了“以后可能会用”把五种模式全选上。第二步使用 Basic Authentication 换取短期 TokenToken 接口从 HTTP Basic Authorization 中读取client_id:client_secret请求体传授权类型和 Scopecurl-XPOSThttps://your-domain/admin-api/system/oauth2/token\-upartner-app:replace-with-secret\-HContent-Type: application/x-www-form-urlencoded\-dgrant_typeclient_credentials\-dscopeorder.read invoice.push后端依次确认授权类型是否受支持Client 是否存在且启用密钥是否一致client_credentials是否在允许模式中本次申请的 Scope 是否为客户端 Scope 的子集。任意一项失败都不发 Token。第三步带 Bearer Token 调受控 API拿到 Access Token 后第三方可以使用标准 Bearer 头。下面假设项目新增了一个显式开放的订单接口curlhttps://your-domain/admin-api/open-api/orders/1001\-HAuthorization: Bearer replace-with-access-token这里有两条必须说清的边界当前工程没有这条/open-api/orders/1001它是推荐的目标形态client_credentials创建的 Token 使用userId0而绝大多数现有管理 API 仍走用户 RBAC因此机器 Token 不能直接当成“全业务通行证”。真正对外开放的接口应建立独立/open-api/*Controller显式写 Scope只返回合作方需要的字段并为机器主体设计可审计的客户端授权模型。第四步按 Client、Token 和 TraceId 追踪接入出现异常时排查顺序应固定在客户端管理确认 Client 状态、授权模式和 Scope在令牌管理确认 Token 的客户端、过期时间和是否被撤销在通用访问日志按时间、请求 URL、用户或 TraceId 定位一次调用再进入业务日志和业务单据检查幂等键、参数和结果。如果要按合作方直接查询还需先把clientId写入日志上下文和日志表当前页面并不具备这个维度。三、设计怎么落地3.1 四张数据表分别承担什么▲ Client 保存接入契约Access/Refresh 保存访问票API Access Log 保存调用事实日志不是权限表关键关系可以概括为一个 Client 可以颁发多张 Access TokenAccess Token 关联 Client、Scope、主体和过期时间Refresh Token 用于需要续期的授权模式client_credentials通常到期后重新换票API 访问日志记录通用请求事实与 Token 表不建立强外键当前也不保存clientId开放平台若需合作方审计应异步补充客户端维度。3.2 换票时先校验客户端契约下面这段对应第二步。Controller 不自己拼授权逻辑而是先解析 Grant Type再统一校验 Client最后分派给对应授权服务ListStringscopesOAuth2Utils.buildScopes(scope);OAuth2GrantTypeEnumgrantTypeEnumOAuth2GrantTypeEnum.getByGrantType(grantType);if(grantTypeEnumnull){throwexception0(BAD_REQUEST.getCode(),StrUtil.format(未知授权类型({}),grantType));}String[]clientobtainBasicAuthorization(request);OAuth2ClientDOclientDOoauth2ClientService.validOAuthClientFromCache(client[0],client[1],grantType,scopes,redirectUri);OAuth2AccessTokenDOaccessToken;switch(grantTypeEnum){caseCLIENT_CREDENTIALS:accessTokenoauth2GrantService.grantClientCredentials(clientDO.getClientId(),scopes);break;caseREFRESH_TOKEN:accessTokenoauth2GrantService.grantRefreshToken(refreshToken,clientDO.getClientId());break;default:thrownewIllegalArgumentException(当前示例只展示机器接入);}validOAuthClientFromCache的价值在于把五类检查收口。新增开放接口时不应再复制一套密钥和 Scope 判断。OAuth2ClientDOclientgetSelf().getOAuth2ClientFromCache(clientId);if(clientnull){throwexception(OAUTH2_CLIENT_NOT_EXISTS);}if(CommonStatusEnum.isDisable(client.getStatus())){throwexception(OAUTH2_CLIENT_DISABLE);}if(StrUtil.isNotEmpty(clientSecret)ObjectUtil.notEqual(client.getSecret(),clientSecret)){throwexception(OAUTH2_CLIENT_CLIENT_SECRET_ERROR);}if(StrUtil.isNotEmpty(grantType)!CollUtil.contains(client.getAuthorizedGrantTypes(),grantType)){throwexception(OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);}if(CollUtil.isNotEmpty(scopes)!CollUtil.containsAll(client.getScopes(),scopes)){throwexception(OAUTH2_CLIENT_SCOPE_OVER);}returnclient;3.3 机器身份为什么使用 userId0客户端模式没有真实员工。当前实现使用管理员用户类型并把用户编号设为0用它表达“这是机器主体”OverridepublicOAuth2AccessTokenDOgrantClientCredentials(StringclientId,ListStringscopes){returnoauth2TokenService.createAccessToken(0L,UserTypeEnum.ADMIN.getValue(),clientId,scopes);}OverridepublicbooleanrevokeToken(StringclientId,StringaccessToken){OAuth2AccessTokenDOtokenoauth2TokenService.getAccessToken(accessToken);if(tokennull||ObjectUtil.notEqual(clientId,token.getClientId())){returnfalse;}returnoauth2TokenService.removeAccessToken(accessToken)!null;}这种实现能在 Token 表中区分“用户票”和“机器票”但还不足以构成可用的服务账号权限体系。审计报表若要精确统计合作方应把clientId带入审计上下文仅看userId0无法区分多个外部系统。3.4 Scope 必须落到接口声明Scope 只有写在 Token 里没有意义。接口需要显式声明最小权限RestControllerRequestMapping(/system/oauth2/user)publicclassOAuth2UserController{GetMapping(/get)PreAuthorize(ss.hasScope(user.read))publicCommonResultOAuth2UserInfoRespVOgetUserInfo(){LonguserIdgetLoginUserId();AdminUserDOuseruserService.getUser(userId);returnsuccess(BeanUtils.toBean(user,OAuth2UserInfoRespVO.class));}PutMapping(/update)PreAuthorize(ss.hasScope(user.write))publicCommonResultBooleanupdateUserInfo(ValidRequestBodyOAuth2UserUpdateReqVOreqVO){userService.updateUserProfile(getLoginUserId(),BeanUtils.toBean(reqVO,UserProfileUpdateReqVO.class));returnsuccess(true);}}读取和修改拆成两个 Scope是开放平台最基本的最小权限原则。3.5 主调用时序▲ 这是建议补齐后的目标链路现有代码已覆盖 Basic 换票显式 M2M 业务 API、完整 Scope 与客户端维度日志仍需建设四、SSO 与开放平台不要混成一件事对照项用户 SSO机器开放接入主体有真实员工第三方应用推荐模式authorization_codeclient_credentials是否需要同意页需要或自动同意不需要是否依赖 redirectUri依赖换票时不依赖权限表达用户角色 Scope显式开放 API Client Scope待补齐到期后处理Refresh Token 静默续期重新用 Client 换票两者可以复用 Client、Token 和校验服务但产品入口、风险模型和审计维度不同。当前 M2M 只有协议换票底座不能把它宣传成已完成的开放 API 产品同时也不应为了绕过userId0的 RBAC 边界强行给合作方创建普通员工账号。五、上线前必须补齐的安全边界Client Secret 不能明文散落当前管理表单能够配置 Secret但生产环境还应做到只在创建或轮换时展示一次数据库加密存储或只保存摘要按协议能力选择Secret 通过密钥管理或 CI Secret 注入不进入 Git每个合作方有独立轮换与吊销流程。开放 Controller 不应直接暴露全部管理 API管理 API 往往包含内部字段、批量导出和越权风险。开放平台应使用独立 VO只暴露合同约定字段并为写接口增加业务幂等键。Scope 不能代替租户和数据范围order.read只说明能读订单不说明能读哪个租户、哪个部门、哪家客户。多租户条件、数据权限和业务归属校验仍要执行。访问日志需要脱敏密码、Secret、身份证、银行卡和完整 Token 不应原样进入请求参数日志。开放平台的“可审计”不能以泄露敏感信息为代价。六、快速体验在线演示地址https://ruoyioffice.com/web/账号admin密码admin123推荐按以下路径体验系统管理 → OAuth 2.0 → 应用管理打开新增表单查看授权类型、Scope、Access/Refresh 有效期和回调 URI进入令牌管理观察 Access Token、客户端编号和过期时间基础设施 → API 日志 → 访问日志打开一条详情对照 TraceId、请求地址、用户、耗时和结果码。常见问题FAQclient_credentials 为什么不需要员工账号因为授权主体是应用本身。当前实现以userId0表示机器主体但现有业务 API 多数依赖用户 RBAC所以“能换票”不等于“已经能调用业务”。还需要独立开放接口和客户端授权模型。有了 OAuth2 Scope还需要 RBAC 吗需要。Scope 应管理外部应用能调用哪类开放接口RBAC 管内部用户角色租户和数据权限还要继续限制数据范围。当前仓库只有两个 Scope 示例接口不能把 Scope 描述成已覆盖全站。Client Secret 可以直接放前端吗不可以。浏览器、小程序和 App 都无法真正保密 Client Secret。client_credentials应由可信后端服务调用。为什么不直接给第三方一个永久 API Key短期 Token 可以过期、撤销和审计Client 也能单独停用。永久 Key 泄露后的风险窗口几乎没有上限。当前系统是否已经自动保护所有 OpenAPI Scope没有。框架提供hasScope能力和示例接口但每个敏感开放接口仍需显式声明 Scope并设计独立开放 VO。结语企业开放平台的核心不是“支持 OAuth2”这五个字而是让每次第三方调用都能回答四个问题谁在调用、能调什么、这张票何时失效、出问题到哪里追。当前 OAuth2 Client、Token 与 Scope 示例已经提供了可靠起点补齐独立 M2M API、客户端授权和clientId审计后第三方接入才会从一次性联调变成可以持续运营、轮换和审计的企业能力。如果这篇对你有用点个「在看」或收藏。演示地址https://ruoyioffice.com/webGitHub 源码https://github.com/yuqing2026/ruoyi-officeGitee 源码https://gitee.com/yqzy1688/ruoyi-office微信17156169080获取产品咨询打开演示地址直接查看系统。
返回列表