六位插槽 —— 插件自定义鉴权的架构跃迁 开源不易如果觉得本项目对您的工作或者学习还是有帮助的话 请帮忙在GitHub 点个⭐️Star, 更多实现细节及完整配置参数详见 GitHub 仓库README和Wiki。gj-pf4j 插件化安全架构全景左侧 Provider 链式认证引擎负责你是谁右侧六位 Filter 插槽负责在什么阶段做什么中间生命周期引擎编排两者的注册与回收三者协同贯穿插件的安全生命周期。gj-pf4j 插件安全架构基于 PF4J 内核深度整合 Spring 生态。不依赖 Spring BootMVC/WebFlux 双栈自适应轻量无侵入对中小项目极友好原生适配达梦、人大金仓、GaussDB 等国产数据库插件代码完全标准 Spring 注解 -可插拔扩展点允许任意第三方组件以零框架改动的方式接入插件生命周期。开放插件自定义鉴权让插件通过 SPI 自由定义认证逻辑Provider 链式委托配合可插拔策略分发请求六位 Filter 插槽供宿主编排任意安全过滤器——受安装 ≠ 启用模型统一管控。温馨提示全文约6100 字左右阅读时长约15分钟。如果你正在为插件设计鉴权方案或者好奇一个插件框架如何把鉴权的事比较优雅地下放给插件——这篇值得你慢慢看一、从一个真实的支付插件说起假设你正在开发一个微信支付插件。它有四个接口POST /wxpay/order/create 创建订单 → 内部员工操作走宿主鉴权GET /wxpay/order/{id} 查询订单 → 同上宿主 session 即可POST /wxpay/order/refund 申请退款 → 内部员工操作但需要额外验 JWT TokenPOST /wxpay/order/callback 支付回调 → 微信服务器调用验 API Key 签名四个接口三种认证方式宿主 Session、JWT Token、API Key 签名。回调接口尤为特殊——它是微信服务器调过来的没有宿主 Session不走登录流程。它只认一个东西你拿着微信发给你的 API Key对请求体做了正确的签名。在之前插件完全做不到这一点。插件的 Controller 注册到宿主 DispatcherServlet请求全部经过同一安全链——宿主用什么鉴权方式插件就只能被动继承。回调接口既不走宿主鉴权又不是完全公开不能直接标 AllowAnonymous卡在中间无法处理。更进一步——即使创建订单走宿主 Session 就够了申请退款需要额外验 JWT。同一个插件内不同接口需要不同的认证策略。这在之前也是做不到的。答案只有一个让插件能定义自己的认证方式但授权仍由宿主统一裁定。二、快速开始宿主引入 gj-pf4j-security 模块即可启用全部鉴权能力插件侧依赖相同scope 改为 provided运行时由宿主提供。io.github.wangpengxpy gj-pf4j-security {version} gj-pf4j 16 项核心能力三、设计边界只做鉴权不做授权这是我们做的第一个关键决策。我们采取集中式策略引擎——从插件各自鉴权迁移到集中化鉴权采用集中化授权框架做统一判定。授权集中化不下放给插件——插件只负责你是谁的认证环节授权决策完全由宿主接管。插件负责 → “你是谁”认证 Authentication宿主负责 → “你能干什么”授权 Authorization回到支付插件的例子插件只管验 API Key 签名、验 JWT Token返回 Authentication 对象。至于这个通过签名验证的微信支付回调有没有权限调接口——由宿主的 AuthorizationFilter 统一裁决。插件不参与授权决策。四、认证模型链式委托与可插拔策略委托式 Provider 链 · 认证编排引擎认证链是委托鉴权的执行内核。下图完整展示了 Provider 链从 supports() 认领、authenticate() 执行到策略裁决、四分支结果的完整流转。4.1 多个认证源支付插件的四个接口需要三种认证方式。同一个请求过来它可能携带 API Key 签名来自微信回调也可能携带 JWT Token来自内部员工的退款申请也可能只有宿主 Session来自内部员工的订单查询。我们的方案是认证提供者链多个 Provider 按优先级排成链依次认领并认证同一个请求。以支付插件为例注册两个 ProviderPOST /wxpay/order/callback 请求到达微信服务器│▼Provider 1 — WxPayCallbackSignatureProvider (order100)supports() → 请求路径是否以 /callback 结尾├─ 否 → 跳过继续下一个└─ 是 → authenticate()├─ 从 Header 取 Wechatpay-Signature├─ 用 API Key 对请求体做 HMAC-SHA256 签名验证├─ 签名匹配 → 注入 SecurityContext → 放行└─ 签名不匹配 → PluginBadCredentialsException → 401Provider 2 — WxPayJwtProvider (order500)supports() → 默认 true兜底认领剩余所有请求authenticate()├─ 从 Header 取 Authorization: Bearer├─ 无 token → 返回 null → 回退宿主鉴权├─ 有 token → 校验 JWT → 注入 SecurityContext└─ token 过期 → PluginBadCredentialsException → WARN 日志继续注意 WxPayCallbackSignatureProvider 的 supports() 只认 /callback 结尾的请求。创建订单、查询订单、退款都不匹配直接跳过交给下一个 Provider。这就是 supports() 的精细认领——只在需要时接管。而 WxPayJwtProvider 作为兜底supports() 默认 true认领所有剩余请求。对于普通订单查询无 JWTauthenticate() 返回 null框架回退到宿主鉴权。对于退款操作有 JWT返回 Authentication 注入上下文。4.2 链的编排策略Provider 链的何时继续、何时终止、最终如何判定不是硬编码的——我们借鉴 Apache Shiro 的 ModularRealmAuthenticator 设计将其抽象为可插拔的 AuthenticationStrategypublic interface AuthenticationStrategy {Decision onAttempt(ProviderAttempt attempt);Authentication onCompletion(List attempts);record ProviderAttempt( IPluginAuthenticationProvider provider, Authentication result, PluginAuthenticationException exception, long durationMs ) {}}框架内置两种策略策略 行为 支付插件中的表现AtLeastOneSuccessfulStrategy默认 所有支持的 Provider 都试任一成功即通过 回调请求WxPayCallbackSignatureProvider 成功 → 放行。退款请求WxPayJwtProvider 成功 → 放行。查询请求两个都无结果 → 回退宿主鉴权FirstSuccessfulStrategy 第一个成功就停 同上但第一个 Provider 成功后立即终止4.3 认证事件与日志可观测性不侵入 Provider每轮 Provider 调用都有完整日志。以支付插件一次回调为例[Plugin: payment] Auth SUCCESS via WxPayCallbackSignatureProvider in 3ms: principalmch_123456[Plugin: payment] Auth chain: 1 success, 0 failed, 3ms total → principalmch_123456退款操作——JWT 已过期[Plugin: payment] Bad credentials via WxPayJwtProvider in 5ms: token expired at 2026-07-05T12:00:00Z[Plugin: payment] Auth chain exhausted: 2 provider(s) all failed in 5ms → 4016 种场景各配 INFO/WARN/ERROR/DEBUG 级别统一 [Plugin: {id}] 前缀每条日志带耗时。更进一步框架发布认证事件——PluginAuthenticationSuccessEvent 和 PluginAuthenticationFailureEvent。宿主监听即可实现告警Componentpublic class PaymentAuthAudit {EventListenerpublic void onCallbackAuth(PluginAuthenticationSuccessEvent e) {if (“WxPayCallbackSignatureProvider”.equals(e.getProviderName())) {auditLog.info(“微信支付回调认证成功: mch{}, {}ms”,e.getAuthentication().getName(), e.getDurationMs());}}EventListener public void onAuthFailure(PluginAuthenticationFailureEvent e) { if (e.getDurationMs() 2000) { alerting.send(插件 [{}] 认证耗时异常: {}ms, e.getPluginId(), e.getDurationMs()); } }}Provider 代码里一行日志都不需要写。事件体系完全在 Provider 外部运作。五、两层 API简单与高级各司其职框架提供两层认证 API适配两种截然不同的开发场景。简单层面向大多数接口——写一个 authenticate() 方法框架自动处理路由分发。高级层面向特殊接口——supports() 精细控制认领范围完全自定义路由逻辑。两层可共存框架自动检测冲突。5.1 简单层只写认证逻辑路由框架自动处理对于大部分插件接口创建订单、查询订单、退款用 JWT 统一认证。开发者只写 authenticate() 方法Componentpublic class WxPayJwtProvider extends AbstractPluginAuthenticationProvider {Overridepublic Authentication authenticate(HttpServletRequest request)throws PluginAuthenticationException {String token request.getHeader(“Authorization”);if (token null || !token.startsWith(Bearer )) {return null; // 无 JWT → 不强制回退宿主鉴权}Claims claims JwtUtils.verify(token.substring(7));return new UsernamePasswordAuthenticationToken(claims.getSubject(), null, extractAuthorities(claims));}}框架在插件启动时自动采集该插件的所有 URL pattern——/wxpay/order/create、/wxpay/order/refund——注入到 supports() 中。路由分发完全自动开发者不需要写一行 if (uri.contains(…))。5.2 高级层精细控制回调接口自主认领回调接口比较特殊——它需要按路径匹配且认证方式完全不同验 API Key 签名而非 JWT。这适合走高级层Order(100) // 比简单层的 WxPayJwtProvider (默认 500) 先执行Componentpublic class WxPayCallbackSignatureProvider implements IPluginAuthenticationProvider {Override public boolean supports(HttpServletRequest request) { return request.getRequestURI().endsWith(/callback); } Override public Authentication authenticate(HttpServletRequest request) throws PluginAuthenticationException { String signature request.getHeader(Wechatpay-Signature); String timestamp request.getHeader(Wechatpay-Timestamp); String nonce request.getHeader(Wechatpay-Nonce); if (signature null || timestamp null || nonce null) { throw new PluginBadCredentialsException(缺少微信支付签名参数); } String body new String(request.getInputStream().readAllBytes()); String expectedSign HmacUtils.hmacSha256(apiKey, timestamp \n nonce \n body); if (!expectedSign.equals(signature)) { throw new PluginBadCredentialsException(微信支付签名验证失败); } return new UsernamePasswordAuthenticationToken(mch_ mchId, null, List.of()); }}关键设计Order(100) 让它在 WxPayJwtProvider默认 500之前执行。回调请求先被 supports() 匹配到认证成功后就结束了WxPayJwtProvider 不会参与。非回调请求不匹配 supports()跳过由后面的 Provider 处理。5.3 两层共存框架自动冲突检测

本月热点