Guardian
轻量级 Spring Boot API 请求层防护框架
防重提交、接口限流、接口幂等、参数自动Trim、慢接口检测、请求链路追踪、IP黑白名单、防重放攻击、接口开关、参数签名、请求加解密 —— 一个 Starter 搞定 API 请求防护。
GitHub ·
Gitee ·
Maven Central
功能一览
| 功能 |
Starter |
注解 |
YAML |
说明 |
| 防重复提交 |
guardian-repeat-submit-spring-boot-starter |
@RepeatSubmit |
✅ |
防止用户重复提交表单/请求 |
| 接口限流 |
guardian-rate-limit-spring-boot-starter |
@RateLimit |
✅ |
滑动窗口 + 令牌桶,双算法可选 |
| 接口幂等 |
guardian-idempotent-spring-boot-starter |
@Idempotent |
— |
Token 机制保证接口幂等性,支持结果缓存 |
| 参数自动Trim |
guardian-auto-trim-spring-boot-starter |
— |
✅ |
自动去除请求参数首尾空格 + 不可见字符替换 |
| 慢接口检测 |
guardian-slow-api-spring-boot-starter |
@SlowApiThreshold |
✅ |
慢接口检测 + Top N 统计 + Actuator 端点 |
| 请求链路追踪 |
guardian-trace-spring-boot-starter |
— |
✅ |
自动生成/透传 TraceId,MDC 日志串联,支持跨线程传递、MQ 链路追踪 |
| IP黑白名单 |
guardian-ip-filter-spring-boot-starter |
— |
✅ |
全局黑名单 + URL 绑定白名单,支持精确/通配符/CIDR |
| 防重放攻击 |
guardian-anti-replay-spring-boot-starter |
— |
✅ |
Timestamp + Nonce 双重校验,nonce TTL 与 timestamp 窗口解耦 |
| 接口开关 |
guardian-api-switch-spring-boot-starter |
— |
✅ |
动态关闭/开启接口 |
| 参数签名 |
guardian-sign-spring-boot-starter |
@SignVerify |
✅ |
支持多种签名算法,请求参数签名验证 + 响应结果签名 |
| 请求加解密 |
guardian-encrypt-spring-boot-starter |
— |
✅ |
支持 RSA+AES / SM2+SM4 加密,请求解密 + 响应加密 |
每个功能独立模块、独立 Starter,用哪个引哪个,互不依赖。所有模块的 YAML 配置均支持配置中心动态刷新(Nacos / Apollo 等),无需重启即可生效。
快速开始
所有starter模块引用
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-starter-all</artifactId>
<version>1.10.0</version>
</dependency>
特别说明:请求链路追踪模块若开启RabbitMq/Kafka/RocketMq请求链路追踪,需额外引入对应的依赖(guardian-trace-rabbitmq/guardian-trace-kafka/guardian-trace-rocketmq)
防重复提交
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-repeat-submit-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
@PostMapping("/submit")
@RepeatSubmit(interval = 10, timeUnit = TimeUnit.SECONDS, message = "订单正在处理,请勿重复提交")
public Result submitOrder(@RequestBody OrderDTO order) {
return orderService.submit(order);
}
SpEL 动态 Key(v1.9.0+):
- 通过
spEl 属性使用 SpEL 表达式动态生成防重 Key
#参数名 - 从请求参数或请求体中获取
#body.参数名 - 显式从请求体中获取
// 仅请求体
@RepeatSubmit(interval = 10, spEl = "#orderId", message = "请勿重复提交")
// 仅请求参数
@GetMapping("/test")
@RepeatSubmit(interval = 10, spEl = "#orderId")
// 组合使用
@RepeatSubmit(interval = 10, spEl = "#userId + ':' + #body.orderId")
YAML 配置:
guardian:
repeat-submit:
urls:
- pattern: /api/order/spel-body
interval: 10
key-scope: user
sp-el: "#orderId"
- pattern: /api/order/spel-mix
interval: 10
key-scope: user
sp-el: "#userId + ':' + #body.orderId"
接口限流
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-rate-limit-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
滑动窗口(默认):
@RateLimit(qps = 1, window = 60, windowUnit = TimeUnit.SECONDS, rateLimitScope = RateLimitKeyScope.IP)
令牌桶:
@RateLimit(qps = 5, capacity = 20, algorithm = RateLimitAlgorithm.TOKEN_BUCKET)
SpEL 动态 Key(v1.9.0+):
- 通过
spEl 属性使用 SpEL 表达式动态生成限流 Key
#参数名 - 从请求参数或请求体中获取
#body.参数名 - 显式从请求体中获取
// 仅请求体
@RateLimit(qps = 5, spEl = "#orderId", message = "操作过于频繁")
// 仅请求参数
@GetMapping("/search")
@RateLimit(qps = 5, spEl = "#keyword")
// 组合使用
@RateLimit(qps = 5, spEl = "#userId + ':' + #body.productId")
YAML 配置:
guardian:
rate-limit:
urls:
- pattern: /api/product/spel-body
qps: 5
rate-limit-scope: global
sp-el: "#productId"
- pattern: /api/order/spel-mix
qps: 5
rate-limit-scope: global
sp-el: "#userId + ':' + #body.productId"
或 YAML 批量配置:
guardian:
rate-limit:
urls:
# 滑动窗口
- pattern: /api/sms/send
qps: 1
window: 60
window-unit: seconds
rate-limit-scope: ip
# 令牌桶
- pattern: /api/seckill/**
qps: 10
capacity: 50
algorithm: token_bucket
rate-limit-scope: global
接口幂等
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-idempotent-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
1. 获取 Token:
GET /guardian/idempotent/token?key=order-submit
2. 业务接口携带 Token:
@Idempotent("order-submit")
@PostMapping("/order/submit")
public Result submitOrder(@RequestBody OrderDTO order) {
return orderService.submit(order);
}
请求头带上 X-Idempotent-Token: {token},首次请求正常处理,重复请求直接拒绝。
参数自动Trim
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-auto-trim-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
零配置即可使用,所有请求参数(表单 + JSON Body)自动去除首尾空格。可选配置不可见字符替换:
guardian:
auto-trim:
exclude-fields:
- password
- signature
character-replacements:
- from: "\\u200B"
to: ""
慢接口检测
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-slow-api-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
零配置即可使用(默认阈值 3000ms),也可通过注解为单个接口自定义阈值:
@SlowApiThreshold(1000)
@GetMapping("/detail")
public Result getDetail(@RequestParam Long id) {
return detailService.query(id);
}
超过阈值自动打印 WARN 日志,通过 GET /actuator/guardianSlowApi 查看 Top N 慢接口排行。
请求链路追踪
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
零配置即可使用,每个请求自动生成 TraceId 并写入 MDC。只需在 logback pattern 中加入 %X{traceId}:
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{36} - %msg%n</pattern>
上游服务通过请求头 X-Trace-Id 传递 TraceId,下游自动复用,实现跨服务链路串联。支持跨线程传递(TraceUtils.wrap() / TraceTaskDecorator),@Async、CompletableFuture、手动线程池场景均可保持 traceId 不丢失。支持 MQ 消息链路追踪(RabbitMQ / Kafka / RocketMQ),发送端自动注入 traceId,消费端通过 AOP 自动提取。
IP黑白名单
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-ip-filter-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
guardian:
ip-filter:
enabled: true
# 全局黑名单(命中直接拒绝所有请求)
black-list:
- 192.168.100.100
- 10.0.0.0/8
# URL 绑定白名单(仅白名单 IP 可访问指定接口)
urls:
- pattern: /admin/**
white-list:
- 127.0.0.1
- 192.168.1.*
匹配优先级:全局黑名单 > URL 绑定白名单 > 放行。IP 规则支持精确匹配、通配符(192.168.1.*)和 CIDR(10.0.0.0/8)。
防重放攻击
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-anti-replay-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
guardian:
anti-replay:
enabled: true
max-age: 60 # timestamp 有效窗口(秒)
nonce-ttl: 86400 # nonce 存活时间(秒,默认 24h)
urls:
- pattern: /api/payment/**
- pattern: /api/transfer/**
客户端请求头携带 X-Timestamp(毫秒时间戳)和 X-Nonce(UUID),服务端校验时间戳有效性 + Nonce 唯一性,双重防护拦截重放请求。
关键设计:nonce-ttl 与 max-age 解耦(默认 24h vs 60s),防止攻击者在 Nonce 过期后篡改 Timestamp 重放。搭配 guardian-sign 签名模块时,nonce-ttl 可缩短至与 max-age 相同。
接口开关
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-api-switch-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
guardian:
api-switch:
enabled: true # 总开关(默认 true)
response-mode: json # exception / json
message: "接口暂时关闭,请稍后再试" # 提示信息(支持 i18n Key)
log-enabled: true # 是否打印拦截日志
interceptor-order: 500 # 拦截器排序
disabled-urls: # 启动时默认关闭的接口
- /api-switch/disabled
参数签名
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-sign-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
使用注解:
// 基本用法
@SignVerify(algorithm = SignAlgorithm.SHA256)
@PostMapping("/submit")
public Result submit(@RequestBody OrderDTO order) {
return orderService.submit(order);
}
// 自定义错误信息
@SignVerify(algorithm = SignAlgorithm.HMAC_SHA256, signVerifyMessage = "签名验证失败,请检查参数")
@PostMapping("/payment")
public Result payment(@RequestBody PaymentDTO payment) {
return paymentService.process(payment);
}
配置示例:
guardian:
sign:
enabled: true # 总开关(默认 true)
secret-key: your-secret-key # 签名密钥
result-sign: true # 结果签名开关(默认 false)
result-advice-order: 100 # 返回值签名 Advice 排序(默认 100)
response-mode: json # exception / json
log-enabled: true # 是否打印拦截日志
interceptor-order: 4000 # 拦截器排序
sign-header: X-Sign # 签名请求头名称
timestamp-header: X-Sign-Timestamp # 时间戳请求头名称
max-age: 60 # 时间戳过期时间(秒)
max-age-unit: seconds # 时间戳过期时间单位
missing-timestamp-message: "缺少时间戳" # 缺少时间戳提示
missing-sign-message: "缺少参数签名" # 缺少签名提示
expired-message: "请求已过期" # 请求过期提示
urls: # 需要签名验证的 URL 规则
- pattern: /api/payment/**
algorithm: hmac_sha256
sign-verify-message: "签名验证失败"
- pattern: /api/order/**
algorithm: md5
sign-verify-message: "订单接口签名验证失败"
详细使用示例:
-
基础签名验证
- 添加依赖和配置
- 在需要验证的接口上添加
@SignVerify 注解
- 客户端需要按照规则生成签名并在请求头中传递
-
结果签名
- 设置
result-sign: true 开启结果签名
- 响应会包含签名信息,客户端可以验证响应的完整性
- 可通过
result-advice-order 配置执行顺序(推荐:幂等缓存 200 → 签名 100 → 加密 300)
-
多算法支持
- 支持多种签名算法:base64、md5、sha256、hmac_sha256
- 可以为不同的接口配置不同的算法
-
时间戳验证
- 自动验证请求时间戳,防止重放攻击
- 可配置时间戳过期时间
-
自定义错误信息
- 可以为不同的接口配置不同的错误提示信息
- 支持国际化消息
防重复提交
展开查看完整文档
使用方式
注解(推荐单接口):
@RepeatSubmit(interval = 10, timeUnit = TimeUnit.SECONDS, message = "请勿重复提交")
YAML(推荐批量):
guardian:
repeat-submit:
urls:
- pattern: /api/order/**
interval: 10
message: "订单正在处理,请勿重复提交"
全量配置
guardian:
repeatable-filter-order: -100000 # 请求体缓存过滤器排序(全局共享,仅需配置一次)
repeat-submit:
storage: redis # redis / local
key-encrypt: md5 # none / md5
response-mode: exception # exception / json
log-enabled: false
interceptor-order: 2000 # 拦截器排序(值越小越先执行)
exclude-urls:
- /api/public/**
urls:
- pattern: /api/order/submit
interval: 10
time-unit: seconds
key-scope: user
message: "请勿重复提交"
防重维度
| 维度 |
YAML 值 |
注解值 |
效果 |
| 用户级 |
user |
KeyScope.USER |
同一用户 + 同一接口 + 同一参数(默认) |
| IP 级 |
ip |
KeyScope.IP |
同一 IP + 同一接口 + 同一参数 |
| 全局级 |
global |
KeyScope.GLOBAL |
同一接口 + 同一参数 |
响应模式
| 模式 |
配置值 |
行为 |
| 异常模式 |
exception(默认) |
抛出 RepeatSubmitException,由全局异常处理器捕获 |
| JSON 模式 |
json |
拦截器直接写入 JSON 响应 |
可观测性
- 拦截日志:
log-enabled: true,前缀 [Guardian-Repeat-Submit]
- Actuator:
GET /actuator/guardianRepeatSubmit
| 参数 |
说明 |
默认值 |
top |
被拦截接口排行返回条数 |
10 |
所有参数均为可选,不传参数与原调用方式完全一致。
{
"totalBlockCount": 128,
"totalPassCount": 5432,
"topBlockedApis": {
"/api/order/submit": 56,
"/api/sms/send": 42
}
}
扩展点
Guardian 的核心组件均可替换,注册同类型 Bean 即可覆盖默认实现。
自定义用户上下文(所有模块共享):
@Bean
public UserContext userContext() {
// 从你的登录体系中获取当前用户 ID
return () -> SecurityUtils.getCurrentUserId();
}
不实现也能用,框架会自动以 SessionId / IP 作为用户标识。
自定义 Key 生成策略:
public class MyKeyGenerator extends AbstractKeyGenerator {
public MyKeyGenerator(UserContext userContext, AbstractKeyEncrypt keyEncrypt) {
super(userContext, keyEncrypt);
}
@Override
protected String buildKey(RepeatSubmitKey key) {
return key.getServletUri() + ":" + key.getUserId();
}
}
@Bean
public MyKeyGenerator myKeyGenerator(UserContext userContext, AbstractKeyEncrypt keyEncrypt) {
return new MyKeyGenerator(userContext, keyEncrypt);
}
自定义 Key 加密策略:
@Bean
public AbstractKeyEncrypt sha256Encrypt() {
return new AbstractKeyEncrypt() {
@Override
public String encrypt(String key) {
return DigestUtil.sha256Hex(key);
}
};
}
自定义存储:
@Bean
public RepeatSubmitStorage myStorage() {
return new RepeatSubmitStorage() {
@Override
public boolean tryAcquire(RepeatSubmitToken token) { /* ... */ }
@Override
public void release(RepeatSubmitToken token) { /* ... */ }
};
}
自定义响应处理器(仅 response-mode: json 时生效):
@Bean
public RepeatSubmitResponseHandler repeatSubmitResponseHandler() {
return (request, response, code, data, message) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));
};
}
接口限流
展开查看完整文档
两种算法
|
滑动窗口(默认) |
令牌桶 |
| 算法 |
统计窗口内请求数,超过阈值拒绝 |
按速率补充令牌,有令牌放行,无令牌拒绝 |
| 突发流量 |
不允许,窗口内严格限制 |
允许,桶满时可瞬间消耗所有令牌 |
| 参数 |
maxCount = qps × window(秒) |
补充速率 = qps / window(秒), capacity = 桶容量 |
| 适合场景 |
精确控速的接口 |
允许突发的场景(秒杀、验证码) |
| 数据结构 |
Local: Deque / Redis: ZSET |
Local: double + synchronized / Redis: HASH |
使用方式
注解:
// 滑动窗口:每秒 10 次,全局
@RateLimit(qps = 10)
// 令牌桶:每秒补 5 个,桶容量 20,允许突发 20 次
@RateLimit(qps = 5, capacity = 20, algorithm = RateLimitAlgorithm.TOKEN_BUCKET)
// 令牌桶 + 分钟级:每分钟补 10 个,桶容量 10
@RateLimit(qps = 10, window = 1, windowUnit = TimeUnit.MINUTES, capacity = 10, algorithm = RateLimitAlgorithm.TOKEN_BUCKET)
YAML:
guardian:
rate-limit:
urls:
- pattern: /api/sms/send
qps: 1
window: 60
window-unit: seconds
rate-limit-scope: ip
- pattern: /api/seckill/**
qps: 10
capacity: 50
algorithm: token_bucket
rate-limit-scope: global
message: "抢购太火爆,请稍后重试"
全量配置
guardian:
rate-limit:
enabled: true # 总开关
storage: redis # redis / local
response-mode: exception # exception / json
log-enabled: false
interceptor-order: 1000 # 拦截器排序(值越小越先执行)
exclude-urls:
- /api/public/**
urls:
- pattern: /api/sms/send
qps: 1
window: 60
window-unit: seconds
rate-limit-scope: ip
- pattern: /api/order/**
qps: 10
rate-limit-scope: user
- pattern: /api/seckill/**
qps: 10
capacity: 50
algorithm: token_bucket
rate-limit-scope: global
注解参数
| 参数 |
默认值 |
说明 |
qps |
10 |
滑动窗口=QPS,令牌桶=每 window 补充的令牌数 |
window |
1 |
滑动窗口=窗口跨度,令牌桶=补充周期 |
windowUnit |
SECONDS |
时间单位 |
algorithm |
SLIDING_WINDOW |
限流算法:SLIDING_WINDOW / TOKEN_BUCKET |
capacity |
-1 |
令牌桶容量,≤0 时取 qps 值 |
rateLimitScope |
GLOBAL |
限流维度 |
message |
请求过于频繁,请稍后再试 |
提示信息 |
限流维度
| 维度 |
YAML 值 |
注解值 |
效果 |
| 全局 |
global |
GLOBAL |
接口维度,不区分用户和 IP(默认) |
| IP |
ip |
IP |
同一 IP 独立计数 |
| 用户 |
user |
USER |
同一用户独立计数 |
可观测性
- 拦截日志:
log-enabled: true,前缀 [Guardian-Rate-Limit]
- Actuator:
GET /actuator/guardianRateLimit
| 参数 |
说明 |
默认值 |
blockedTop |
被拦截接口排行返回条数 |
10 |
requestTop |
高频请求接口排行返回条数 |
10 |
detailsTop |
接口维度明细返回条数 |
10 |
所有参数均为可选,不传参数与原调用方式完全一致。
{
"totalRequestCount": 5560,
"totalPassCount": 5432,
"totalBlockCount": 128,
"blockRate": "2.30%",
"topBlockedApis": { "/api/sms/send": 56 },
"topRequestApis": { "/api/search": 3200 },
"apiDetails": {
"/api/sms/send": { "requests": 200, "passes": 144, "blocks": 56, "blockRate": "28.00%" }
}
}
扩展点
注册同类型 Bean 即可覆盖默认实现。
自定义用户上下文(与防重模块共享):
@Bean
public UserContext userContext() {
return () -> SecurityUtils.getCurrentUserId();
}
自定义限流 Key 生成策略:
public class MyRateLimitKeyGenerator extends AbstractRateLimitKeyGenerator {
public MyRateLimitKeyGenerator(UserContext userContext) {
super(userContext);
}
@Override
protected String buildKey(RateLimitKey key) {
return key.getServletUri() + ":" + key.getUserId();
}
}
@Bean
public MyRateLimitKeyGenerator myRateLimitKeyGenerator(UserContext userContext) {
return new MyRateLimitKeyGenerator(userContext);
}
自定义存储:
@Bean
public RateLimitStorage myRateLimitStorage() {
return new RateLimitStorage() {
@Override
public boolean tryAcquire(RateLimitToken token) { /* ... */ }
};
}
自定义响应处理器(仅 response-mode: json 时生效):
@Bean
public RateLimitResponseHandler rateLimitResponseHandler() {
return (request, response, code, data, message) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));
};
}
接口幂等
展开查看完整文档
工作流程
- 客户端调用
GET /guardian/idempotent/token?key=order-submit 获取一次性 Token
- 客户端携带 Token 发起业务请求(Header 或 Param 方式)
- 拦截器校验 Token:存在则消费放行,不存在或已消费则拒绝
- (可选)开启结果缓存后,重复请求返回首次执行结果而非拒绝
注解参数
| 参数 |
默认值 |
说明 |
value |
必填 |
接口唯一标识,用于隔离不同接口的 Token |
from |
HEADER |
Token 来源:HEADER / PARAM(PARAM 模式依次查找 URL 参数、表单字段、JSON Body) |
tokenName |
X-Idempotent-Token |
Header 名 / URL 参数名 / JSON Body 字段名 |
message |
幂等Token无效或已消费 |
拒绝时的提示信息 |
全量配置
guardian:
repeatable-filter-order: -100000 # 请求体缓存过滤器排序(全局共享,仅需配置一次)
idempotent:
enabled: true # 总开关
storage: redis # redis / local
timeout: 300 # Token 有效期(默认 300)
time-unit: seconds # 有效期单位
response-mode: exception # exception / json
log-enabled: false
interceptor-order: 3000 # 拦截器排序(值越小越先执行)
result-advice-order: 200 # 返回值缓存 Advice 排序(默认 200)
token-endpoint: true # 是否注册内置 Token 获取接口
result-cache: false # 是否启用结果缓存
missing-token-message: "请求缺少幂等Token" # 缺少 Token 时的提示(支持 i18n Key)
结果缓存
开启 result-cache: true 后,首次请求的返回值会被缓存,重复请求直接返回缓存结果(而非拒绝)。
可观测性
- 拦截日志:
log-enabled: true,前缀 [Guardian-Idempotent]
- Actuator:
GET /actuator/guardianIdempotent
| 参数 |
说明 |
默认值 |
top |
被拦截接口排行返回条数 |
10 |
所有参数均为可选,不传参数与原调用方式完全一致。
{
"totalRequestCount": 1200,
"totalPassCount": 1100,
"totalBlockCount": 100,
"blockRate": "8.33%",
"topBlockedApis": {
"/order/submit": 60,
"/pay/confirm": 40
}
}
扩展点
自定义 Token 生成器:
@Bean
public IdempotentTokenGenerator idempotentTokenGenerator() {
return () -> "custom-" + UUID.randomUUID().toString();
}
自定义存储:
@Bean
public IdempotentStorage myIdempotentStorage() {
return new IdempotentStorage() {
@Override
public void save(IdempotentToken token) { /* ... */ }
@Override
public boolean tryConsume(String tokenKey) { /* ... */ }
};
}
自定义响应处理器(仅 response-mode: json 时生效):
@Bean
public IdempotentResponseHandler idempotentResponseHandler() {
return (request, response, code, data, message) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));
};
}
参数自动Trim
展开查看完整文档
功能说明
自动去除所有请求参数的首尾空格,同时支持不可见字符替换(如零宽空格、BOM、回车符等从复制粘贴混入的脏字符)。同时作用于表单参数(Query / Form)和 JSON Body。
全量配置
guardian:
auto-trim:
enabled: true # 总开关(默认 true)
filter-order: -10000 # Filter 排序(值越小越先执行)
exclude-fields: # 排除字段(表单 + JSON Body 统一生效)
- password
- signature
character-replacements: # 字符替换规则(先替换后 trim)
- from: "\\r" # 回车符
to: ""
- from: "\\u200B" # 零宽空格
to: ""
- from: "\\uFEFF" # BOM
to: ""
字符替换规则
character-replacements 支持以下转义格式:
| 转义写法 |
实际字符 |
说明 |
\\r |
\r |
回车符 |
\\n |
\n |
换行符 |
\\t |
\t |
制表符 |
\\0 |
\0 |
空字符 |
\\uXXXX |
Unicode 字符 |
如 \\u200B = 零宽空格 |
执行顺序:先执行字符替换,再执行 trim。
排除字段
密码、签名等不应被 trim 的字段可加入 exclude-fields,同时作用于表单参数名和 JSON Body 字段名:
guardian:
auto-trim:
exclude-fields:
- password
- signature
慢接口检测
展开查看完整文档
功能说明
自动检测响应时间超过阈值的接口,打印 WARN 日志并记录统计数据。支持全局阈值 + 注解覆盖,通过 Actuator 端点查看 Top N 排行。
使用方式
零配置:引入 Starter 即可使用,默认阈值 3000ms。
注解自定义阈值:
@SlowApiThreshold(1000)
@GetMapping("/detail")
public Result getDetail(@RequestParam Long id) {
return detailService.query(id);
}
注解优先级高于全局配置。没有注解的接口使用全局阈值。
全量配置
guardian:
slow-api:
enabled: true # 总开关(默认 true)
threshold: 3000 # 全局阈值(毫秒,默认 3000)
interceptor-order: -1000 # 拦截器排序(值越小越先执行)
exclude-urls: # 排除规则(白名单,命中直接放行)
- /api/health
- /api/public/**
可观测性
日志输出:超过阈值自动打印 WARN 日志,前缀 [Guardian-Slow-Api]:
WARN [Guardian-Slow-Api] @SlowApiThreshold 慢接口检测 | Method=GET | URI=/api/detail | 耗时=3521ms | 阈值=3000ms
Actuator 端点:GET /actuator/guardianSlowApi
| 参数 |
说明 |
默认值 |
top |
慢接口排行返回条数 |
10 |
recordTop |
每个接口的耗时明细记录条数 |
5(最大 100) |
所有参数均为可选,不传参数与原调用方式完全一致。
{
"totalSlowCount": 15,
"topSlowApis": {
"/api/detail": {
"count": 8,
"maxDuration": 5230,
"recentRecords": [
{ "duration": 5230, "time": "2026-02-09 15:32:10" },
{ "duration": 4100, "time": "2026-02-09 14:20:45" }
]
},
"/api/export": {
"count": 7,
"maxDuration": 12500,
"recentRecords": [
{ "duration": 12500, "time": "2026-02-09 11:05:22" }
]
}
}
}
扩展点
自定义慢接口记录器(SPI):
默认使用内存环形缓冲记录慢接口数据,可通过注入自定义 Bean 替换为数据库 / ES 等持久化方案:
@Bean
public SlowApiRecorder slowApiRecorder() {
return new SlowApiRecorder() {
@Override
public void record(SlowApiRecord record) { /* 写入数据库 / ES */ }
@Override
public long getTotalSlowCount() { /* ... */ }
@Override
public LinkedHashMap<String, Map<String, Object>> getTopSlowApis(int n) { /* ... */ }
@Override
public List<SlowApiRecord> getRecords(String uri, int limit) { /* ... */ }
};
}
请求链路追踪
展开查看完整文档
功能说明
自动为每个请求生成唯一的 TraceId,写入 MDC 和响应头。上游服务通过请求头传递 TraceId,下游自动复用,实现跨服务日志串联。内置跨线程传递工具类,@Async、CompletableFuture、手动线程池等场景均可保持 traceId 不丢失。
使用方式
零配置:引入 Starter 即可使用。
在 Logback 配置中加入 %X{traceId} 即可在日志中打印 TraceId:
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{36} - %msg%n</pattern>
工作流程
请求进入
│
▼
TraceIdFilter(OncePerRequestFilter)
├─ 请求头有 X-Trace-Id → 复用(上游透传)
└─ 请求头没有 → 自动生成(时分秒 + 10位随机字符串)
│
▼
MDC.put("traceId", traceId) ← 写入 MDC,日志自动携带
response.setHeader(headerName) ← 写入响应头,客户端可获取
│
▼
业务执行(同一线程内所有日志都带 traceId)
│
▼
MDC.remove("traceId") ← 请求结束清理
跨服务链路串联
上游服务调用下游时,把响应头中的 TraceId 传递到下游请求头即可:
服务 A 收到请求 → 生成 traceId=abc123 → 调用服务 B 时带上 X-Trace-Id: abc123
服务 B 收到请求 → 从请求头取出 abc123 → 复用同一个 traceId
同一条链路上所有服务的日志都带 abc123,排查问题时按 TraceId 搜索即可。
跨线程 TraceId 传递
MDC 基于 ThreadLocal,新开子线程时 traceId 会丢失。Guardian 提供以下工具解决:
1. 手动包装 Runnable / Callable
executor.submit(TraceUtils.wrap(() -> {
// 子线程中 traceId 与父线程一致
log.info("异步任务执行");
}));
Future<String> future = executor.submit(TraceUtils.wrap(() -> {
log.info("带返回值的异步任务");
return "result";
}));
2. 包装 Executor(适用于 CompletableFuture)
Executor tracedExecutor = TraceUtils.wrap(myExecutor);
CompletableFuture.runAsync(() -> {
log.info("CompletableFuture 中也有 traceId");
}, tracedExecutor);
3. @Async 线程池自动传递
@Bean
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setTaskDecorator(new TraceTaskDecorator());
executor.initialize();
return executor;
}
配置 TraceTaskDecorator 后,所有 @Async 任务自动携带父线程的 traceId,无需手动包装。
| 类名 |
场景 |
用法 |
TraceRunnable |
手动提交 Runnable |
new TraceRunnable(task) 或 TraceUtils.wrap(task) |
TraceCallable |
手动提交 Callable |
new TraceCallable<>(task) 或 TraceUtils.wrap(task) |
TraceTaskDecorator |
@Async 线程池 |
设置到 ThreadPoolTaskExecutor.setTaskDecorator() |
TraceUtils |
工具类 |
wrap(Runnable) / wrap(Callable) / wrap(Executor) / getTraceId() / switchTraceId() |
MQ 消息链路追踪
Guardian 支持 RabbitMQ、Kafka、RocketMQ 三种消息队列的 TraceId 自动传递,发送端自动注入 traceId,消费端通过 AOP 切面自动提取并写入 MDC。
核心设计:消费端采用 AOP 切面拦截 Listener 方法,不占用框架原生拦截器/Hook 位置,接入方可自由注册自己的拦截器。
注意:AOP 切面从方法参数中提取 traceId,因此 Listener 方法的参数类型必须使用消息原始类型:
- RabbitMQ:
Message(org.springframework.amqp.core.Message),不能用 String
- Kafka:
ConsumerRecord<?, ?>,不能用 String
- RocketMQ:
MessageExt(org.apache.rocketmq.common.message.MessageExt)
RabbitMQ
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-rabbitmq</artifactId>
<version>1.10.0</version>
</dependency>
- 发送端:通过
MessagePostProcessor 将 traceId 写入消息 Header(additive,不冲突)
- 消费端:AOP 切面拦截
@RabbitListener / @RabbitHandler 方法,自动从消息 Header 提取 traceId
Kafka
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-kafka</artifactId>
<version>1.10.0</version>
</dependency>
发送端需在 Kafka Producer 配置中注册拦截器:
spring:
kafka:
producer:
properties:
interceptor.classes: com.sun.guardian.trace.kafka.interceptor.TraceKafkaProducerInterceptor
- 发送端:通过
ProducerInterceptor 将 traceId 写入消息 Header(不占用 RecordInterceptor)
- 消费端:AOP 切面拦截
@KafkaListener / @KafkaHandler 方法,自动从消息 Header 提取 traceId
RocketMQ
<dependency>
<groupId>io.github.biggg-guardian</groupId>
<artifactId>guardian-trace-rocketmq</artifactId>
<version>1.10.0</version>
</dependency>
- 发送端:通过
SendMessageHook 将 traceId 写入消息 UserProperty(支持注册多个 Hook,不冲突)
- 消费端:AOP 切面拦截
@RocketMQMessageListener 注解的类,自动从消息 UserProperty 提取 traceId
批量消费场景
AOP 切面自动设置第一条消息的 traceId。如需在批量消费中逐条切换 traceId,各模块提供了专用工具类,一行代码即可完成切换:
| 模块 |
工具类 |
用法 |
| RabbitMQ |
TraceRabbitUtils |
TraceRabbitUtils.switchTraceId(message) |
| Kafka |
TraceKafkaUtils |
TraceKafkaUtils.switchTraceId(record) |
| RocketMQ |
TraceRocketMQUtils |
TraceRocketMQUtils.switchTraceId(messageExt) |
工具类内部自动从消息 Header / UserProperty 中提取 traceId 并切换 MDC,headerName 通过 TraceConfig 动态获取,支持配置中心热更新。
RabbitMQ 批量消费示例:
@RabbitListener(queues = "myQueue", containerFactory = "batchContainerFactory")
public void handleBatch(List<Message> messages) {
for (Message msg : messages) {
TraceRabbitUtils.switchTraceId(msg);
// 业务逻辑...
}
}
Kafka 批量消费示例:
@KafkaListener(topics = "myTopic", batch = "true")
public void handleBatch(List<ConsumerRecord<String, String>> records) {
for (ConsumerRecord<String, String> record : records) {
TraceKafkaUtils.switchTraceId(record);
// 业务逻辑...
}
}
RocketMQ 说明:RocketMQ Spring Boot Starter 的 RocketMQListener 本身是逐条回调,AOP 切面会自动为每次调用注入/清理 traceId,通常无需手动切换。如有特殊场景,也可使用 TraceRocketMQUtils.switchTraceId(messageExt)。
全量配置
guardian:
trace:
enabled: true # 总开关(默认 true)
filter-order: -30000 # Filter 排序(值越小越先执行,确保最先执行)
header-name: X-Trace-Id # 请求头/响应头名称(默认 X-Trace-Id)
IP黑白名单
展开查看完整文档
功能说明
基于 IP 的访问控制,支持两种模式:
- 全局黑名单:匹配的 IP 拒绝访问所有接口
- URL 绑定白名单:指定接口仅允许白名单 IP 访问,其余 IP 拒绝
匹配优先级:全局黑名单 > URL 绑定白名单 > 放行
IP 规则支持三种格式:
| 格式 |
示例 |
说明 |
| 精确匹配 |
192.168.1.100 |
精确匹配单个 IP |
| 通配符 |
192.168.1.* |
匹配整个网段 |
| CIDR |
10.0.0.0/8 |
CIDR 网段匹配 |
全量配置
guardian:
ip-filter:
enabled: true # 总开关(默认 false,需显式开启)
response-mode: json # exception / json
log-enabled: true # 是否打印拦截日志(默认 false)
message: "IP 访问被拒绝" # 拒绝提示信息(支持 i18n Key)
filter-order: -20000 # Filter 排序(值越小越先执行)
black-list: # 全局 IP 黑名单
- 192.168.100.100
- 10.0.0.0/8
urls: # URL 绑定白名单
- pattern: /admin/**
white-list:
- 127.0.0.1
- 192.168.1.*
- pattern: /internal/api/**
white-list:
- 10.0.0.0/8
可观测性
- 拦截日志:
log-enabled: true,前缀 [Guardian-Ip-Filter]
- Actuator:
GET /actuator/guardianIpFilter
| 参数 |
说明 |
默认值 |
blockTop |
黑名单拦截 IP 排行返回条数 |
10 |
whiteTop |
白名单拦截请求排行返回条数 |
10 |
所有参数均为可选,不传参数与原调用方式完全一致。
{
"totalBlackListBlockCount": 56,
"totalWhiteListBlockCount": 23,
"topBlackListBlocked": {
"192.168.100.100": 42,
"10.1.2.3": 14
},
"topWhiteListBlocked": {
"/admin/dashboard | 172.16.0.5": 15,
"/internal/api/config | 192.168.2.1": 8
}
}
扩展点
自定义响应处理器(仅 response-mode: json 时生效):
@Bean
public IpFilterResponseHandler ipFilterResponseHandler() {
return (request, response, code, data, message) -> {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));
};
}
防重放攻击
展开查看完整文档
功能说明
防止攻击者截获合法请求后重复发送(Replay Attack)。客户端在请求头中携带时间戳和一次性随机标识(Nonce),服务端进行双重校验:
- Timestamp 校验:请求时间戳与服务器时间差超过
max-age 则拒绝(请求过期)
- Nonce 校验:Nonce 已存在于存储中则拒绝(重放攻击),不存在则记录并放行
安全设计:Nonce 的存活时间(nonce-ttl,默认 24h)远大于 Timestamp 有效窗口(max-age,默认 60s)。二者解耦可防止攻击者在 Nonce 过期后篡改 Timestamp 重放请求。搭配 guardian-sign 签名模块使用时,签名防止 Timestamp 被篡改,nonce-ttl 可缩短至与 max-age 相同。
校验流程
请求进入 AntiReplayFilter
│
├─ 1. 排除规则匹配(exclude-urls)→ 命中直接放行
│
├─ 2. URL 规则匹配(urls)→ 未命中则放行
│
├─ 3. 取 X-Timestamp,判断与服务器时间差
│ ↓ 超过 maxAge → 拒绝(请求过期)
│
├─ 4. 取 X-Nonce,判断是否已使用
│ ↓ Storage 中已存在 → 拒绝(重放攻击)
│
├─ 5. 校验通过
│ 将 Nonce 存入 Storage(TTL = nonceTtl)
│ 放行
▼
客户端请求示例
POST /api/payment/submit HTTP/1.1
X-Timestamp: 1708000000000
X-Nonce: a1b2c3d4e5f6g7h8
Content-Type: application/json
{"orderId": "ORD001", "amount": 99.99}
全量配置
guardian:
anti-replay:
enabled: true # 总开关(默认 true)
storage: redis # redis / local
max-age: 60 # timestamp 有效窗口(默认 60)
max-age-unit: seconds # timestamp 有效窗口单位(默认秒)
nonce-ttl: 86400 # nonce 存活时间(默认 86400 = 24h,必须 >= max-age)
nonce-ttl-unit: seconds # nonce 存活时间单位(默认秒)
timestamp-header: X-Timestamp # 时间戳请求头名称
nonce-header: X-Nonce # Nonce 请求头名称
response-mode: json # exception / json
log-enabled: true # 是否打印拦截日志
filter-order: -14000 # Filter 排序
missing-timestamp-message: "缺少时间戳" # 提示信息(支持 i18n Key)
missing-nonce-message: "缺少请求标识"
expired-message: "请求已过期"
replay-message: "重复请求"
urls: # 需要防重放的 URL(AntPath)
- pattern: /api/payment/**
- pattern: /api/transfer/**
exclude-urls: # 排除规则(白名单,优先级最高)
- /api/public/**
可观测性
- 拦截日志:
log-enabled: true,前缀 [Guardian-Anti-Replay]
- Actuator:
GET /actuator/guardianAntiReplay
| 参数 |
说明 |
默认值 |
top |
被拦截接口排行返回条数 |
10 |
所有参数均为可选,不传参数与原调用方式完全一致。
{
"totalRequestCount": 5000,
"totalPassCount": 4980,
"totalBlockCount": 20,
"blockRate": "0.40%",
"topBlockedApis": {
"/api/payment/submit": 12,
"/api/transfer/confirm": 8
}
}
扩展点
自定义 Nonce 存储:
@Bean
public NonceStorage nonceStorage() {
return new NonceStorage() {
@Override
public boolean tryAcquire(String nonce, long nonceTtl, TimeUnit nonceTtlUnit) {
// 自定义存储逻辑(如 Memcached、数据库等)
}
};
}
自定义响应处理器(仅 response-mode: json 时生效):
@Bean
public AntiReplayResponseHandler antiReplayResponseHandler() {
return (request, response, code, data, message) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));
};
}
接口开关
展开查看完整文档
功能说明
基于yml/actuator实现接口的关闭/开启控制,接口路径支持AntPath
全量配置
guardian:
api-switch:
enabled: true # 总开关(默认 true)
response-mode: json # exception / json
message: "接口暂时关闭,请稍后再试" # 提示信息(支持 i18n Key)
log-enabled: true # 是否打印拦截日志
interceptor-order: 500 # 拦截器排序
disabled-urls: # 启动时默认关闭的接口
- /api-switch/disabled
可观测性
- 拦截日志:
log-enabled: true,前缀 [Guardian-Api-Switch]
- Actuator:
GET /actuator/guardianApiSwitch
{
"disabledUrls": [
"/api-switch/disabled"
]
}
关闭接口
- Actuator:
POST /actuator/guardianApiSwitch/{urlPattern}
关闭接口需将接口内{urlPattern}替换为实际接口路径,例如:/actuator/guardianApiSwitch/api-switch/disabled
{
"action": "disabled",
"urlPattern": "/api-switch/disabled",
"disabledUrls": [
"/123",
"/api-switch/disabled"
]
}
开启接口
- Actuator:
DELETE /actuator/guardianApiSwitch/{urlPattern}
开启接口需将接口内{urlPattern}替换为实际接口路径,例如:/actuator/guardianApiSwitch/api-switch/disabled
{
"action": "enabled",
"urlPattern": "/api-switch/disabled",
"disabledUrls": [
"/123"
]
}
扩展点
自定义响应处理器(仅 response-mode: json 时生效):
@Bean
public ApiSwitchResponseHandler apiSwitchResponseHandler() {
return (request, response, code, data, message) -> {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));
};
}
参数签名
展开查看完整文档
功能说明
对接口请求参数进行签名验证,防止参数被篡改。支持多种签名算法,包括 BASE64、MD5、SHA256 和 HMAC-SHA256。同时支持响应结果签名,确保返回数据的完整性。
使用方式
注解:
// 使用默认算法(BASE64)
@SignVerify
// 指定算法
@SignVerify(algorithm = SignAlgorithm.SHA256)
// 自定义错误信息
@SignVerify(algorithm = SignAlgorithm.HMAC_SHA256, signVerifyMessage = "签名验证失败")
YAML 批量配置:
guardian:
sign:
urls:
- pattern: /api/payment/**
algorithm: hmac_sha256
sign-verify-message: "支付接口签名验证失败"
- pattern: /api/order/**
algorithm: sha256
全量配置
guardian:
sign:
enabled: true # 总开关(默认 true)
secret-key: your-secret-key # 签名密钥
result-sign: false # 结果签名开关(默认 false)
response-mode: exception # exception / json
log-enabled: false # 是否打印拦截日志
interceptor-order: 4000 # 拦截器排序(值越小越先执行)
sign-header: X-Sign # 签名请求头名称
timestamp-header: X-Sign-Timestamp # 时间戳请求头名称
max-age: 60 # 时间戳过期时间(秒)
max-age-unit: seconds # 时间戳过期时间单位
missing-timestamp-message: "缺少时间戳" # 缺少时间戳提示(支持 i18n Key)
missing-sign-message: "缺少参数签名" # 缺少签名提示(支持 i18n Key)
expired-message: "请求已过期" # 请求过期提示(支持 i18n Key)
urls: # 需要签名验证的 URL 规则
- pattern: /api/payment/**
algorithm: hmac_sha256
sign-verify-message: "签名验证失败"
支持的签名算法
| 算法 |
YAML 值 |
注解值 |
说明 |
| BASE64 |
base64 |
SignAlgorithm.BASE64 |
基于 BASE64 的简单编码 |
| MD5 |
md5 |
SignAlgorithm.MD5 |
MD5 哈希算法 |
| SHA256 |
sha256 |
SignAlgorithm.SHA256 |
SHA256 哈希算法 |
| HMAC-SHA256 |
hmac_sha256 |
SignAlgorithm.HMAC_SHA256 |
基于密钥的 HMAC-SHA256 算法 |
| SM3 |
sm3 |
SignAlgorithm.SM3 |
国密 SM3 哈希算法 |
签名计算规则
- 参数排序:对请求参数(包括 Query 参数和 JSON Body)按字典序排序
- 拼接字符串:
param1=value1¶m2=value2×tamp={timestamp}&key={secretKey}
- 计算签名:使用指定的算法对拼接后的字符串进行计算
- 验证签名:将计算结果与请求头中的
X-Sign 进行比较
前端实现参考
前端实现请参考示例项目中的测试页面:
- 文件位置:
guardian-example/src/main/resources/static/sign-test.html
- 关键依赖:CryptoJS(MD5/SHA256/HMAC-SHA256)、sm-crypto(SM3)
核心实现要点:
- BASE64 算法(GET 请求):只使用 Query 参数,不使用 Body
- 其他算法(POST 请求):只使用 Body,不使用 Query 参数
- 响应验签:后端对整个响应体进行签名,前端需使用完整响应数据验签
响应结果签名
开启 result-sign: true 后,响应结果会自动添加签名头:
X-Sign:响应结果的签名
X-Sign-Timestamp:签名时的时间戳
特别说明:结果参数签名时会将controller返回的所有信息都进行签名,包括code、message、data等完整的响应结构。例如:
{
"code": 200,
"message": "success",
"data": {
"request": {
"name": "guardian",
"value": "test123"
},
"message": "This response is signed"
},
"timestamp": 1772534163534
}
签名会基于上述完整的JSON结构进行计算,确保整个响应结果的完整性。
可观测性
- 拦截日志:
log-enabled: true,前缀 [Guardian-Sign]
- Actuator:
GET /actuator/guardianSign
{
"totalRequestCount": 1000,
"totalPassCount": 990,
"totalBlockCount": 10,
"blockRate": "1.00%",
"topBlockedApis": {
"/api/payment/submit": 5,
"/api/order/create": 3
}
}
扩展点
自定义签名服务:
@Bean
public SignService customSignService() {
return new SignService() {
@Override
public String sign(SortedMap<String, String> params, String timestamp, String secretKey, SignAlgorithm algorithm) {
// 自定义签名逻辑
}
};
}
自定义响应处理器(仅 response-mode: json 时生效):
@Bean
public SignResponseHandler signResponseHandler() {
return (request, response, code, data, message) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));
};
}
请求加解密
展开查看完整文档
功能说明
对接口请求参数进行解密,对响应结果进行加密,确保数据传输的安全性。支持两种加密方案:
- RSA + AES:RSA 加密 AES 密钥,AES 加密数据
- SM2 + SM4:国密算法,SM2 加密 SM4 密钥,SM4 加密数据
使用方式
YAML 配置:
guardian:
# 请求解密配置
decrypt:
enabled: true
key:
private-key: "your-private-key-base64" # 解密密钥时使用的私钥
data:
data-key-header: "X-Encrypt-Key" # 数据密钥请求头名称
urls:
- pattern: /api/sensitive/**
key-algorithm: rsa # 密钥加密算法:rsa / sm2
data-algorithm: aes # 数据加密算法:aes / sm4
# 响应加密配置
encrypt:
enabled: true
key:
public-key: "your-public-key-base64" # 加密密钥时使用的公钥
data:
data-key-header: "X-Encrypt-Key" # 数据密钥响应头名称
urls:
- pattern: /api/sensitive/**
key-algorithm: rsa
data-algorithm: aes
全量配置
guardian:
decrypt:
enabled: true # 总开关(默认 true)
filter-order: -40000 # Filter 排序(默认 -40000)
log-enabled: false # 是否打印拦截日志(默认 false)
response-mode: exception # 响应模式:exception / json(默认 exception)
key:
private-key: "" # 私钥(Base64 格式)
data:
param-alias: "encryptParam" # param 数据加密后别名(默认 encryptParam)
body-alias: "encryptBody" # body 数据加密后别名(默认 encryptBody)
data-key-header: "X-Encrypt-Key" # 数据密钥请求头名称(默认 X-Encrypt-Key)
missing-data-key-header-message: "缺少数据密钥请求头" # 缺少密钥头提示
urls: # 需要解密的 URL 规则
- pattern: /api/sensitive/**
key-algorithm: rsa
data-algorithm: aes
exclude-urls: # 排除规则(白名单)
- /api/public/**
encrypt:
enabled: true # 总开关(默认 true)
result-advice-order: 300 # 返回值加密 Advice 排序(默认 300)
key:
public-key: "" # 公钥(Base64 格式)
data:
param-alias: "encryptParam" # param 数据加密后别名(默认 encryptParam)
body-alias: "encryptBody" # body 数据加密后别名(默认 encryptBody)
data-key-header: "X-Encrypt-Key" # 数据密钥响应头名称(默认 X-Encrypt-Key)
key-mode: dynamic # 密钥模式:dynamic / static(默认 dynamic)
key: "" # 静态密钥(key-mode=static 时使用)
urls: # 需要加密的 URL 规则
- pattern: /api/sensitive/**
key-algorithm: rsa
data-algorithm: aes
exclude-urls: # 排除规则(白名单)
- /api/public/**
支持的加密算法
| 密钥算法 |
YAML 值 |
说明 |
| RSA |
rsa |
RSA 加密 AES 密钥 |
| SM2 |
sm2 |
国密 SM2 加密 SM4 密钥 |
| 数据算法 |
YAML 值 |
说明 |
| AES |
aes |
AES-GCM 模式加密数据 |
| SM4 |
sm4 |
国密 SM4-CBC 模式加密数据 |
工作流程
请求解密流程:
客户端请求
│
├─ 1. 生成随机数据密钥(AES/SM4)
├─ 2. 使用数据密钥加密请求参数(param 或 body)
├─ 3. 使用服务端公钥加密数据密钥
├─ 4. 发送请求:
│ Header: X-Encrypt-Key: {加密后的密钥}
│ Param: encryptParam={加密后的参数} 或
│ Body: {"encryptBody": "{加密后的数据}"}
│
▼
服务端 DecryptFilter
│
├─ 1. 从 Header 获取加密的密钥
├─ 2. 使用私钥解密得到数据密钥
├─ 3. 从 param 或 body 中获取加密数据
├─ 4. 使用数据密钥解密数据
├─ 5. 将解密后的数据传递给业务层
│
▼
业务处理
响应加密流程:
业务层返回数据
│
▼
EncryptResponseAdvice
│
├─ 1. 生成随机数据密钥(或使用静态密钥)
├─ 2. 使用数据密钥加密响应数据
├─ 3. 使用客户端公钥加密数据密钥
├─ 4. 返回响应:
│ Header: X-Encrypt-Key: {加密后的密钥}
│ Body: {"encryptBody": "{加密后的数据}"}
│
▼
客户端
│
├─ 1. 从 Header 获取加密的密钥
├─ 2. 使用私钥解密得到数据密钥
├─ 3. 使用数据密钥解密响应数据
│
▼
业务处理
密钥模式
支持两种密钥模式:
| 模式 |
YAML 值 |
说明 |
| 动态密钥 |
dynamic |
每次请求动态生成密钥(默认) |
| 静态密钥 |
static |
使用配置的固定密钥 |
前端实现参考
前端实现请参考示例项目中的测试页面:
- 文件位置:
guardian-example/src/main/resources/static/encrypt-test.html
- 关键依赖:CryptoJS(AES)、sm-crypto(SM2/SM4)、jsencrypt(RSA)
核心实现要点:
-
RSA + AES 方案
- 生成 16 字节 AES 密钥
- 使用 AES-GCM 模式加密数据
- 使用 RSA 公钥加密 AES 密钥
-
SM2 + SM4 方案
- 生成 16 字节 SM4 密钥
- 使用 SM4-CBC 模式加密数据
- 使用 SM2 公钥加密 SM4 密钥(C1C3C2 格式)
扩展点
自定义密钥解密服务:
@Bean
public KeyDecryptService customKeyDecryptService() {
return new KeyDecryptService() {
@Override
public String decrypt(String encryptedKey, String privateKey) {
// 自定义密钥解密逻辑
}
@Override
public KeyEncryptAlgorithm getAlgorithm() {
return KeyEncryptAlgorithm.RSA;
}
};
}
自定义数据解密服务:
@Bean
public DataDecryptService customDataDecryptService() {
return new DataDecryptService() {
@Override
public String decrypt(String encryptedData, String key) {
// 自定义数据解密逻辑
}
@Override
public DataEncryptAlgorithm getAlgorithm() {
return DataEncryptAlgorithm.AES;
}
};
}
规则优先级
Guardian 各模块(防重复提交、接口限流、慢接口检测)的规则匹配遵循以下优先级:
exclude-urls(白名单)> YAML 规则 > 注解 > 放行
| 场景 |
行为 |
URL 命中 exclude-urls |
直接放行,跳过所有检测(包括注解) |
URL 命中 YAML urls 规则 |
YAML 规则生效 |
方法有注解 @RateLimit / @RepeatSubmit / @SlowApiThreshold |
注解规则生效 |
| 以上均未命中 |
放行 |
设计理念:exclude-urls 作为白名单拥有最高优先级,可在紧急情况下通过配置中心动态添加 URL 实现"一键放行",无需改代码重启。注解适合"长期固定"的保护策略,YAML 规则适合"动态可调"的批量策略。
动态配置
Guardian 所有模块的 YAML 配置均支持通过配置中心(Nacos、Apollo 等)动态刷新,无需重启应用即可生效。
支持动态刷新的配置项
以下列出所有模块可在配置中心动态修改的完整参数(enabled、storage 等启动期参数修改后需重启生效)。
防重复提交(prefix: guardian.repeat-submit)
| YAML Key |
类型 |
默认值 |
说明 |
response-mode |
exception / json |
exception |
响应模式 |
log-enabled |
boolean |
false |
是否打印拦截日志 |
exclude-urls |
List<String> |
[] |
排除规则(白名单,优先级最高,AntPath) |
urls |
List |
[] |
防重规则列表,每项参数如下 |
urls[].pattern |
String |
— |
接口路径(AntPath) |
urls[].interval |
int |
5 |
防重间隔 |
urls[].time-unit |
TimeUnit |
seconds |
间隔时间单位 |
urls[].key-scope |
user / ip / global |
user |
防重维度 |
urls[].sp-el |
String |
— |
SpEL 表达式,动态生成 Key(v1.9.0+,如 #orderId) |
urls[].message |
String |
您的请求过于频繁,请稍后再试 |
拦截提示信息 |
urls[].client-type |
pc / app |
pc |
客户端类型 |
接口限流(prefix: guardian.rate-limit)
| YAML Key |
类型 |
默认值 |
说明 |
response-mode |
exception / json |
exception |
响应模式 |
log-enabled |
boolean |
false |
是否打印拦截日志 |
exclude-urls |
List<String> |
[] |
排除规则(白名单,优先级最高,AntPath) |
urls |
List |
[] |
限流规则列表,每项参数如下 |
urls[].pattern |
String |
— |
接口路径(AntPath) |
urls[].qps |
int |
10 |
滑动窗口=QPS,令牌桶=每 window 补充令牌数 |
urls[].window |
int |
1 |
滑动窗口=窗口跨度,令牌桶=补充周期 |
urls[].window-unit |
TimeUnit |
seconds |
时间单位 |
urls[].algorithm |
sliding_window / token_bucket |
sliding_window |
限流算法 |
urls[].capacity |
int |
-1 |
令牌桶容量(≤0 时取 qps 值) |
urls[].rate-limit-scope |
global / ip / user |
global |
限流维度 |
urls[].sp-el |
String |
— |
SpEL 表达式,动态生成 Key(v1.9.0+,如 #orderId) |
urls[].message |
String |
请求过于频繁,请稍后再试 |
拦截提示信息 |
接口幂等(prefix: guardian.idempotent)
| YAML Key |
类型 |
默认值 |
说明 |
timeout |
long |
300 |
Token 有效期 |
time-unit |
TimeUnit |
seconds |
有效期单位 |
response-mode |
exception / json |
exception |
响应模式 |
log-enabled |
boolean |
false |
是否打印拦截日志 |
参数自动Trim(prefix: guardian.auto-trim)
| YAML Key |
类型 |
默认值 |
说明 |
exclude-fields |
Set<String> |
[] |
排除字段(不做 trim 的字段名) |
character-replacements |
List |
[] |
字符替换规则列表,每项参数如下 |
character-replacements[].from |
String |
— |
待替换字符(支持 \\r \\n \\t \\uXXXX 转义) |
character-replacements[].to |
String |
"" |
替换为 |
慢接口检测(prefix: guardian.slow-api)
| YAML Key |
类型 |
默认值 |
说明 |
threshold |
long |
3000 |
全局慢接口阈值(毫秒) |
exclude-urls |
List<String> |
[] |
排除规则(白名单,优先级最高,AntPath) |
请求链路追踪(prefix: guardian.trace)
| YAML Key |
类型 |
默认值 |
说明 |
header-name |
String |
X-Trace-Id |
请求头/响应头名称(同时影响 MQ 链路追踪的 Header Key) |
防重放攻击(prefix: guardian.anti-replay)
| YAML Key |
类型 |
默认值 |
说明 |
max-age |
long |
60 |
timestamp 有效窗口 |
max-age-unit |
TimeUnit |
seconds |
timestamp 有效窗口单位 |
nonce-ttl |
long |
86400 |
nonce 存活时间(必须 >= max-age) |
nonce-ttl-unit |
TimeUnit |
seconds |
nonce 存活时间单位 |
response-mode |
exception / json |
exception |
响应模式 |
log-enabled |
boolean |
false |
是否打印拦截日志 |
missing-timestamp-message |
String |
缺少时间戳 |
缺少时间戳提示(支持 i18n Key) |
missing-nonce-message |
String |
缺少请求标识 |
缺少 Nonce 提示(支持 i18n Key) |
expired-message |
String |
请求已过期 |
请求过期提示(支持 i18n Key |
README 内容过长,已截取部分内容。完整内容请前往 GitHub 仓库查看。
评论