轻量级 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 等),无需重启即可生效。
<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},首次请求正常处理,重复请求直接拒绝。
<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 自动提取。
<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)));
};
}展开查看完整文档
自动去除所有请求参数的首尾空格,同时支持不可见字符替换(如零宽空格、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 搜索即可。
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() |
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)
<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
<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
<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 拒绝访问所有接口
- 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: sha256guardian:
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: aesguardian:
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) |
replay-message |
String |
重复请求 |
重放攻击提示(支持 i18n Key) |
urls |
List |
[] |
防重放 URL 规则列表(AntPath) |
exclude-urls |
List<String> |
[] |
排除规则(白名单,优先级最高) |
IP黑白名单(prefix: guardian.ip-filter)
| YAML Key | 类型 | 默认值 | 说明 |
|---|---|---|---|
response-mode |
exception / json |
exception |
响应模式 |
log-enabled |
boolean |
false |
是否打印拦截日志 |
message |
String |
IP 访问被拒绝 |
拒绝提示信息(支持 i18n Key) |
black-list |
List<String> |
[] |
全局 IP 黑名单(精确 / 通配符 / CIDR) |
urls |
List |
[] |
URL 绑定白名单规则列表,每项参数如下 |
urls[].pattern |
String |
— | 接口路径(AntPath) |
urls[].white-list |
List<String> |
[] |
允许访问的 IP 列表(精确 / 通配符 / CIDR) |
接口开关(prefix: guardian.api-switch)
| YAML Key | 类型 | 默认值 | 说明 |
|---|---|---|---|
response-mode |
exception / json |
exception |
响应模式 |
log-enabled |
boolean |
false |
是否打印拦截日志 |
message |
String |
接口暂时关闭,请稍后再试 |
提示信息(支持 i18n Key) |
disabled-urls |
List<String> |
[] |
默认关闭的接口路径(AntPath) |
参数签名(prefix: guardian.sign)
| YAML Key | 类型 | 默认值 | 说明 |
|---|---|---|---|
secret-key |
String |
"" |
签名密钥 |
response-mode |
exception / json |
exception |
响应模式 |
log-enabled |
boolean |
false |
是否打印拦截日志 |
sign-header |
String |
X-Sign |
签名请求头名称 |
timestamp-header |
String |
X-Sign-Timestamp |
时间戳请求头名称 |
max-age |
long |
60 |
时间戳过期时间 |
max-age-unit |
TimeUnit |
seconds |
时间戳过期时间单位 |
missing-timestamp-message |
String |
缺少时间戳 |
缺少时间戳提示(支持 i18n Key) |
missing-sign-message |
String |
缺少参数签名 |
缺少签名提示(支持 i18n Key) |
expired-message |
String |
请求已过期 |
请求过期提示(支持 i18n Key) |
urls |
List |
[] |
签名验证规则列表,每项参数如下 |
urls[].pattern |
String |
— | 接口路径(AntPath) |
urls[].algorithm |
base64 / md5 / sha256 / hmac_sha256 / sm3 |
base64 |
签名算法 |
urls[].sign-verify-message |
String |
签名校验失败 |
签名验证失败提示(支持 i18n Key) |
请求解密(prefix: guardian.decrypt)
| YAML Key | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
boolean |
true |
总开关 |
filter-order |
int |
-40000 |
Filter 排序(值越小越先执行) |
log-enabled |
boolean |
false |
是否打印拦截日志 |
response-mode |
exception / json |
exception |
响应模式 |
key.private-key |
String |
"" |
私钥(Base64 格式,用于解密密钥) |
data.param-alias |
String |
encryptParam |
param 数据加密后别名 |
data.body-alias |
String |
encryptBody |
body 数据加密后别名 |
data.data-key-header |
String |
X-Encrypt-Key |
数据密钥请求头名称 |
missing-data-key-header-message |
String |
缺少数据密钥请求头 |
缺少密钥头提示(支持 i18n Key) |
urls |
List |
[] |
解密规则列表,每项参数如下 |
urls[].pattern |
String |
— | 接口路径(AntPath) |
urls[].key-algorithm |
rsa / sm2 |
rsa |
密钥加密算法 |
urls[].data-algorithm |
aes / sm4 |
aes |
数据加密算法 |
exclude-urls |
List<String> |
[] |
排除规则(白名单,优先级最高,AntPath) |
响应加密(prefix: guardian.encrypt)
| YAML Key | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
boolean |
true |
总开关 |
result-advice-order |
int |
300 |
返回值加密 Advice 排序 |
key.public-key |
String |
"" |
公钥(Base64 格式,用于加密密钥) |
data.param-alias |
String |
encryptParam |
param 数据加密后别名 |
data.body-alias |
String |
encryptBody |
body 数据加密后别名 |
data.data-key-header |
String |
X-Encrypt-Key |
数据密钥响应头名称 |
data.key-mode |
dynamic / static |
dynamic |
密钥模式:动态生成 / 静态配置 |
data.key |
String |
"" |
静态密钥(key-mode=static 时使用) |
urls |
List |
[] |
加密规则列表,每项参数如下 |
urls[].pattern |
String |
— | 接口路径(AntPath) |
urls[].key-algorithm |
rsa / sm2 |
rsa |
密钥加密算法 |
urls[].data-algorithm |
aes / sm4 |
aes |
数据加密算法 |
exclude-urls |
List<String> |
[] |
排除规则(白名单,优先级最高,AntPath) |
以 Nacos 为例,引入 Spring Cloud Alibaba Nacos Config 依赖后,在 Nacos 控制台修改 Guardian 相关配置并发布,应用会自动感知变更并即时生效。
1. 添加依赖(以 Spring Boot 2.7.x 为例):
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>2. 配置 bootstrap.yml:
spring:
application:
name: your-app
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yml3. 在 Nacos 控制台修改配置:
在对应的 Data ID(如 your-app.yml)中修改 Guardian 配置并发布,发布后即时生效,无需重启。以下是覆盖全模块的 Nacos 配置示例:
guardian:
repeat-submit:
response-mode: exception
log-enabled: false
exclude-urls:
- /api/public/**
urls:
- pattern: /api/order/**
interval: 10
time-unit: seconds
key-scope: user
message: "请勿重复提交"
# SpEL 动态 Key 示例(v1.9.0+)
- 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"
rate-limit:
response-mode: exception
log-enabled: false
exclude-urls:
- /api/public/**
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: "抢购太火爆,请稍后重试"
# SpEL 动态 Key 示例(v1.9.0+)
- 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"
idempotent:
timeout: 300
time-unit: seconds
response-mode: exception
log-enabled: false
missing-token-message: guardian.idempotent.missing-token
auto-trim:
exclude-fields:
- password
- signature
character-replacements:
- from: "\\u200B"
to: ""
- from: "\\uFEFF"
to: ""
slow-api:
threshold: 3000
exclude-urls:
- /api/health
trace:
header-name: X-Trace-Id
ip-filter:
enabled: true
response-mode: json
log-enabled: true
message: "IP 访问被拒绝"
black-list:
- 192.168.100.100
urls:
- pattern: /admin/**
white-list:
- 127.0.0.1
- 192.168.1.*
anti-replay:
max-age: 60
nonce-ttl: 86400
response-mode: json
log-enabled: true
missing-timestamp-message: "缺少时间戳"
missing-nonce-message: "缺少请求标识"
expired-message: "请求已过期"
replay-message: "重复请求"
urls:
- pattern: /api/payment/**
- pattern: /api/transfer/**
exclude-urls:
- /api/public/**
api-switch:
response-mode: json
message: "接口暂时关闭,请稍后再试"
log-enabled: true
disabled-urls:
- /api-switch/disabled
sign:
secret-key: your-secret-key
response-mode: exception
log-enabled: false
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:
- pattern: /api/payment/**
algorithm: hmac_sha256
sign-verify-message: "签名校验失败"
- pattern: /api/order/**
algorithm: md5
sign-verify-message: "签名校验失败"
decrypt:
enabled: true
filter-order: -40000
log-enabled: false
response-mode: exception
key:
private-key: "your-private-key-base64"
data:
param-alias: "encryptParam"
body-alias: "encryptBody"
data-key-header: "X-Encrypt-Key"
missing-data-key-header-message: "缺少数据密钥请求头"
urls:
- pattern: /api/sensitive/**
key-algorithm: rsa
data-algorithm: aes
- pattern: /api/government/**
key-algorithm: sm2
data-algorithm: sm4
exclude-urls:
- /api/public/**
encrypt:
enabled: true
result-advice-order: 300
key:
public-key: "your-public-key-base64"
data:
param-alias: "encryptParam"
body-alias: "encryptBody"
data-key-header: "X-Encrypt-Key"
key-mode: dynamic
key: ""
urls:
- pattern: /api/sensitive/**
key-algorithm: rsa
data-algorithm: aes
- pattern: /api/government/**
key-algorithm: sm2
data-algorithm: sm4
exclude-urls:
- /api/public/**只需配置你用到的模块,没用到的模块无需配置。修改任意参数后点击发布,下一次请求即可读取到最新值。
Guardian 的 @ConfigurationProperties 属性类实现了模块配置接口(如 RepeatSubmitConfig、RateLimitConfig 等),拦截器/过滤器通过接口引用动态读取配置值。当配置中心推送变更时,Spring Cloud 的 ConfigurationPropertiesRebinder 自动重新绑定属性,所有引用该配置的组件在下次请求时即可读取到最新值。
Guardian 的拒绝响应消息(防重、限流、幂等)支持 Spring 标准的 MessageSource 国际化机制。不使用国际化的用户无需任何改动,message 配置中文纯文本即可,行为与之前完全一致。
message 字段同时支持纯文本和 i18n Key 两种写法:
# 纯文本(默认,不走国际化)
message: "请求过于频繁,请稍后再试"
# i18n Key(自动走 MessageSource 解析)
message: guardian.rate-limit.rejectedGuardian 通过 GuardianMessageResolver 统一解析:尝试从 MessageSource 查找,找到则返回对应语言的翻译,找不到则原样返回。语言由请求头 Accept-Language 自动决定。
第一步,把 message 改成 i18n Key。
YAML 规则示例:
guardian:
rate-limit:
urls:
- pattern: /api/sms/send
qps: 1
message: guardian.rate-limit.rejected
repeat-submit:
urls:
- pattern: /api/order/submit
interval: 10
message: guardian.repeat-submit.rejected
idempotent:
missing-token-message: guardian.idempotent.missing-token注解示例:
@RateLimit(qps = 5, message = "guardian.rate-limit.rejected")
@RepeatSubmit(interval = 10, message = "guardian.repeat-submit.rejected")
@Idempotent(value = "createOrder", message = "guardian.idempotent.rejected")第二步,在项目中添加多语言消息文件。
重要:必须创建基础文件
messages.properties,Spring Boot 的MessageSourceAutoConfiguration需要检测到该文件才会激活ResourceBundleMessageSource,否则国际化不生效。
# src/main/resources/messages.properties(必须,作为默认回退)
guardian.rate-limit.rejected=请求过于频繁,请稍后再试
guardian.repeat-submit.rejected=您的请求过于频繁,请稍后再试
guardian.idempotent.rejected=幂等Token无效或已消费
guardian.idempotent.missing-token=请求缺少幂等Token# src/main/resources/messages_zh_CN.properties
guardian.rate-limit.rejected=请求过于频繁,请稍后再试
guardian.repeat-submit.rejected=您的请求过于频繁,请稍后再试
guardian.idempotent.rejected=幂等Token无效或已消费
guardian.idempotent.missing-token=请求缺少幂等Token# src/main/resources/messages_en.properties
guardian.rate-limit.rejected=Rate limit exceeded, please try again later
guardian.repeat-submit.rejected=Too many requests, please try again later
guardian.idempotent.rejected=Idempotent token is invalid or already consumed
guardian.idempotent.missing-token=Missing idempotent token第三步,通过请求头 Accept-Language 切换语言:
| 请求头 | 匹配文件 | 效果 |
|---|---|---|
Accept-Language: zh-CN |
messages_zh_CN.properties |
中文 |
Accept-Language: en |
messages_en.properties |
英文 |
| 不传 | messages.properties |
默认回退 |
Spring Boot 自动根据 Accept-Language 匹配语言,无需额外配置。
如果项目使用自定义消息文件路径(如
i18n/messages),只需配置spring.messages.basename=i18n/messages,Guardian 自动适配。同样需要确保基础文件i18n/messages.properties存在。
guardian-parent
├── guardian-core # 公共基础(共享类)
├── guardian-repeat-submit/ # 防重复提交
│ ├── guardian-repeat-submit-core/
│ └── guardian-repeat-submit-spring-boot-starter/
├── guardian-rate-limit/ # 接口限流
│ ├── guardian-rate-limit-core/
│ └── guardian-rate-limit-spring-boot-starter/
├── guardian-idempotent/ # 接口幂等
│ ├── guardian-idempotent-core/
│ └── guardian-idempotent-spring-boot-starter/
├── guardian-auto-trim/ # 参数自动Trim
│ ├── guardian-auto-trim-core/
│ └── guardian-auto-trim-spring-boot-starter/
├── guardian-slow-api/ # 慢接口检测
│ ├── guardian-slow-api-core/
│ └── guardian-slow-api-spring-boot-starter/
├── guardian-trace/ # 请求链路追踪
│ ├── guardian-trace-core/
│ ├── guardian-trace-spring-boot-starter/
│ ├── guardian-trace-rabbitmq/ # RabbitMQ 链路追踪
│ ├── guardian-trace-kafka/ # Kafka 链路追踪
│ └── guardian-trace-rocketmq/ # RocketMQ 链路追踪
├── guardian-ip-filter/ # IP黑白名单
│ ├── guardian-ip-filter-core/
│ └── guardian-ip-filter-spring-boot-starter/
├── guardian-anti-replay/ # 防重放攻击
│ ├── guardian-anti-replay-core/
│ └── guardian-anti-replay-spring-boot-starter/
├── guardian-api-switch/ # 接口开关
│ ├── guardian-api-switch-core/
│ └── guardian-api-switch-spring-boot-starter/
├── guardian-sign/ # 参数签名
│ ├── guardian-sign-core/
│ └── guardian-sign-spring-boot-starter/
├── guardian-encrypt/ # 请求加解密
│ ├── guardian-encrypt-core/
│ └── guardian-encrypt-spring-boot-starter/
├── guardian-starter-all/ # 整合引用所有Starter模块
├── guardian-storage-redis/ # Redis 存储(多模块共享)
└── guardian-example/ # 示例工程
Guardian 的 Filter 和 Interceptor 通过 order 值控制执行优先级,值越小越先执行。
Filter 在 Servlet 层执行,先于所有 Interceptor:
| 顺序 | 模块 | 配置项 | 默认 order | 说明 |
|---|---|---|---|---|
| 1 | 请求体缓存 | guardian.repeatable-filter-order |
-100000 | 最先执行,缓存请求体供后续模块重复读取 |
| 2 | 请求解密 | guardian.decrypt.filter-order |
-40000 | 解密请求数据 |
| 3 | 请求链路追踪 | guardian.trace.filter-order |
-30000 | 为后续所有操作提供 TraceId |
| 4 | IP 黑白名单 | guardian.ip-filter.filter-order |
-20000 | 拦截恶意 IP,尽早阻断 |
| 5 | 防重放攻击 | guardian.anti-replay.filter-order |
-14000 | 校验 Timestamp + Nonce,拦截重放请求 |
| 6 | 参数自动 Trim | guardian.auto-trim.filter-order |
-10000 | 参数预处理,在业务逻辑前清洗数据 |
Interceptor 在 Spring MVC 层执行,Filter 之后:
| 顺序 | 模块 | 配置项 | 默认 order | 说明 |
|---|---|---|---|---|
| 1 | 慢接口检测 | guardian.slow-api.interceptor-order |
-1000 | 最先进入最后退出,精确计算整体耗时 |
| 2 | 接口开关 | guardian.api-switch.interceptor-order |
500 | 接口关闭直接拒绝,避免后续无意义计算 |
| 3 | 接口限流 | guardian.rate-limit.interceptor-order |
1000 | 流量超限直接拒绝,避免后续无意义计算 |
| 4 | 防重复提交 | guardian.repeat-submit.interceptor-order |
2000 | 通过限流后,判断是否短时间重复请求 |
| 5 | 接口幂等 | guardian.idempotent.interceptor-order |
3000 | 消费 Token 不可逆,确保前面校验都通过 |
| 6 | 参数签名 | guardian.sign.interceptor-order |
4000 | 签名验证,确保请求参数未被篡改 |
每个模块的 order 均可通过 YAML 自定义,方便与项目中其他拦截器协调:
guardian:
# Filter 排序
repeatable-filter-order: -100000 # 请求体缓存(最先执行,全局共享)
decrypt:
filter-order: -40000 # 请求解密
trace:
filter-order: -30000 # 链路追踪
ip-filter:
filter-order: -20000 # IP 黑白名单
anti-replay:
filter-order: -14000 # 防重放攻击
auto-trim:
filter-order: -10000 # 参数自动 Trim
# Interceptor 排序
slow-api:
interceptor-order: -1000 # 慢接口检测
api-switch:
interceptor-order: 500 # 接口开关
rate-limit:
interceptor-order: 1000 # 接口限流
repeat-submit:
interceptor-order: 2000 # 防重复提交
idempotent:
interceptor-order: 3000 # 接口幂等
sign:
interceptor-order: 4000 # 参数签名
# ResponseBodyAdvice 排序
idempotent:
result-advice-order: 200 # 幂等结果缓存(需开启 result-cache)
sign:
result-advice-order: 100 # 响应结果签名(需开启 result-sign)
encrypt:
result-advice-order: 300 # 响应结果加密| Redis | Local | |
|---|---|---|
| 分布式 | 支持 | 仅单机 |
| 持久性 | Redis 持久化 | 重启丢失 |
| 推荐场景 | 生产环境 | 开发/单体应用 |
| 额外依赖 | 需要 Redis | 无 |
- 新增:请求加解密模块(
guardian-encrypt),支持 RSA+AES / SM2+SM4 加密方案 - 新增:请求解密过滤器(
DecryptFilter),自动解密请求参数 - 新增:响应加密 Advice(
EncryptResponseAdvice),自动加密响应结果 - 新增:密钥加密服务(
KeyEncryptService),支持 RSA 和 SM2 算法 - 新增:数据加密服务(
DataEncryptService),支持 AES-GCM 和 SM4-CBC 算法 - 新增:加密模块 Actuator 端点(
GET /actuator/guardianEncrypt) - 新增:前端示例页面(
sign-test.html、encrypt-test.html),提供完整的前端实现参考 - 新增:签名模块支持 SM3 算法
- 优化:签名模块前端实现文档简化,指向示例页面
- 优化:加密模块支持动态密钥和静态密钥两种模式
- 新增:防重复提交模块支持 SpEL 表达式动态生成 Key(
spEl属性) - 新增:接口限流模块支持 SpEL 表达式动态生成 Key(
spEl属性) - 新增:
SpElUtils工具类,支持#参数名和#body.参数名访问请求参数
- 新增:ResponseBodyAdvice 执行顺序可配置,通过 YAML 动态控制
- 新增:签名模块
result-advice-order配置项(默认100) - 新增:幂等模块
result-advice-order配置项(默认200) - 修复:修复
IdempotentResultCacheAdvice未添加@ControllerAdvice注解导致 Advice 不生效的问题 - 修复:修复
SignResultSignAdviceAdvice 执行顺序不可控的问题 - 优化:完善拦截器与 Advice 执行顺序说明
- 新增:参数签名模块(
guardian-sign),支持多种签名算法,请求参数签名验证 + 响应结果签名 - 新增:
@SignVerify注解,支持指定签名算法和自定义错误信息 - 新增:签名验证拦截器(
SignVerifyInterceptor),自动校验请求签名 - 新增:响应结果签名(
SignResultSignAdvice),确保返回数据的完整性 - 新增:签名服务(
SignService),支持 BASE64、MD5、SHA256 和 HMAC-SHA256 四种算法 - 新增:签名统计和 Actuator 端点(
GET /actuator/guardianSign) - 修复:HMAC-SHA256 算法实现错误,添加了密钥参数
- 修复:配置验证,添加了对 secretKey 的非空检查
- 修复:响应签名空值检查缺失问题
- 新增:接口开关模块(
guardian-api-switch),动态关闭/开启接口
- 新增:整合所有starter模块(
guardian-starter-all),以便于引用所有功能时只需引用此模块 - 优化:共享类重新分类分包
- 新增:防重放攻击模块(
guardian-anti-replay-spring-boot-starter),通过 Timestamp + Nonce 双重校验拦截重放请求 - 新增:
NonceStorage存储接口,支持 Redis(分布式)和 Local(单机)两种实现 - 新增:
nonce-ttl与max-age解耦设计,Nonce 存活时间(默认 24h)远大于 Timestamp 有效窗口(默认 60s),防止攻击者在 Nonce 过期后篡改 Timestamp 重放 - 新增:
nonce-ttl-unit/max-age-unit支持自定义时间单位,validate()方法统一换算后比较 - 新增:防重放攻击 Actuator 监控端点(
GET /actuator/guardianAntiReplay),展示拦截统计和 Top N 被拦截接口 - 新增:防重放攻击拦截日志(
log-enabled: true,前缀[Guardian-Anti-Replay]) - 新增:防重放攻击 URL 规则 + 排除规则(
urls+exclude-urls),支持 AntPath 匹配 - 新增:
AntiReplayResponseHandlerSPI 扩展点,支持自定义拒绝响应格式 - 新增:全部拒绝提示信息支持 i18n Key(
missing-timestamp-message/missing-nonce-message/expired-message/replay-message)
- 新增:MQ 消息链路追踪,支持 RabbitMQ、Kafka、RocketMQ 三种消息队列的 TraceId 自动传递
- 新增:
guardian-trace-rabbitmq模块,发送端通过 MessagePostProcessor 注入 traceId,消费端通过 AOP 切面拦截@RabbitListener自动提取 - 新增:
guardian-trace-kafka模块,发送端通过 ProducerInterceptor 注入 traceId,消费端通过 AOP 切面拦截@KafkaListener自动提取 - 新增:
guardian-trace-rocketmq模块,发送端通过 SendMessageHook 注入 traceId,消费端通过 AOP 切面拦截@RocketMQMessageListener自动提取 - 新增:
TraceUtils.switchTraceId()方法,支持 MQ 批量消费场景逐条切换 traceId - 新增:
TraceRabbitUtils/TraceKafkaUtils/TraceRocketMQUtils批量消费专用工具类,一行代码从消息中提取并切换 traceId - 新增:慢接口记录器 SPI 扩展点(
SlowApiRecorder),支持自定义持久化方案(数据库 / ES 等)替换默认内存实现 - 优化:MQ 消费端统一采用 AOP 切面拦截,不占用框架原生拦截器/Hook 位置,与用户自定义拦截器互不冲突
- 优化:Kafka ProducerInterceptor 通过持有 TraceConfig 引用动态获取 headerName,支持配置中心热更新
- 修复:MQ 自动配置类添加
@AutoConfigureAfter,确保在TraceConfig和 MQ Template Bean 创建之后加载,解决因加载顺序导致的链路追踪不生效问题
- 重构:移除
hutool-all依赖,全面切换 Spring/JDK/Jackson 原生工具,项目零外部工具包依赖 - 新增:
GuardianJsonUtils(基于 Jackson)、IpUtils、Md5Utils三个核心工具类 - 修复:
jackson-databind作用域由provided改为compile,解决下游模块编译失败
- 新增:IP 黑白名单模块(
guardian-ip-filter-spring-boot-starter),支持全局 IP 黑名单 + URL 绑定白名单 - 新增:IP 规则匹配支持精确匹配、通配符(
192.168.1.*)和 CIDR(10.0.0.0/8)三种格式 - 新增:IP 黑白名单拦截统计 + Actuator 端点(
GET /actuator/guardianIpFilter) - 新增:
IpMatcher工具类,统一处理 IP 规则匹配逻辑 - 新增:IP 黑白名单拦截日志(
log-enabled: true,前缀[Guardian-Ip-Filter])
- 新增:消息国际化支持,拒绝响应消息(防重、限流、幂等)支持 Spring
MessageSource国际化,message 字段可配置 i18n Key,根据Accept-Language自动匹配语言 - 新增:
GuardianMessageResolver消息解析工具,MessageSource 能解析则返回翻译,否则原样返回,不使用国际化的用户零感知 - 新增:幂等模块
missing-token-message配置项,缺少 Token 时的提示信息支持自定义及国际化
- 优化:
GuardianRateLimitProperties/GuardianRepeatSubmitProperties删除多余无用参数
- 新增:全模块配置动态刷新支持,配合 Nacos / Apollo 等配置中心可在不重启应用的情况下实时更新 Guardian 配置
- 新增:
BaseConfig/BaseCharacterReplacement配置接口,拦截器/过滤器通过接口引用动态读取配置,与 Spring CloudConfigurationPropertiesRebinder无缝集成 - 优化:
CharacterSanitizer引入基于哈希值的缓存机制,配置未变更时零解析开销,配置动态刷新后自动重建 - 修复:
DefaultIdempotentTokenService改为持有IdempotentConfig接口引用,修复 Token 过期时间无法动态更新的问题
- 新增:参数自动Trim模块(
guardian-auto-trim-spring-boot-starter),自动去除请求参数首尾空格,支持表单参数 + JSON Body - 新增:不可见字符替换功能(
character-replacements),可清除零宽空格、BOM、回车符等从复制粘贴混入的不可见字符 - 新增:排除字段配置(
exclude-fields),密码、签名等敏感字段可跳过 Trim - 新增:慢接口检测模块(
guardian-slow-api-spring-boot-starter),超过阈值自动记录并打印 WARN 日志 - 新增:
@SlowApiThreshold注解,支持为单个接口自定义慢接口阈值 - 新增:慢接口 Actuator 端点(
GET /actuator/guardianSlowApi),展示触发次数 + Top N 排行 + 最大耗时 - 新增:请求链路追踪模块(
guardian-trace-spring-boot-starter),自动生成/透传 TraceId - 新增:TraceId 写入 MDC + 响应头,Logback pattern 加
%X{traceId}即可串联全链路日志 - 新增:上游服务通过
X-Trace-Id请求头透传 TraceId,支持跨服务链路追踪
- 新增:接口幂等模块(
guardian-idempotent-spring-boot-starter),Token 机制保证接口幂等性 - 新增:幂等结果缓存,开启后重复请求返回首次执行结果
- 新增:幂等 Actuator 端点、拦截统计、Token 生成器可插拔
- 新增:幂等 PARAM 模式支持从 JSON Body 解析 Token(兼容 form-data / x-www-form-urlencoded / JSON 三种 POST 传参)
- 优化:
RepeatableRequestFilter排序提升到全局配置GuardianCoreProperties(guardian.repeatable-filter-order),仅需配置一次 - 优化:拦截器执行顺序可配置(
interceptor-order),默认:限流 1000 → 防重 2000 → 幂等 3000 - 优化:三模块 Properties 提取公共基类
BaseGuardianProperties,统一 storage / responseMode / logEnabled / interceptorOrder - 优化:移除 Manager 中间层(
KeyGeneratorManager、RateLimitKeyGeneratorManager、KeyEncryptManager、IdempotentTokenGeneratorManager),改为构造函数直接注入 - 优化:删除未使用的异常类(
TokenGeneratorNotFoundException、KeyGeneratorNotFoundException、KeyEncryptNotFoundException) - 修复:幂等 null 返回值处理,与 Spring 原生行为保持一致
- 修复:
BaseResult.error()状态码错误(200 → 500) - 修复:Actuator Endpoint ID 改为驼峰命名,消除 Spring Boot 启动 WARN
- 修复:三模块参数校验(
qps、window、interval、timeout≤ 0 时抛出明确异常) - 修复:令牌桶算法时间回拨防护(Redis Lua + 本地存储均加
max(0, elapsed))
- 新增:接口限流令牌桶算法(
TOKEN_BUCKET),支持突发流量 - 修复:本地滑动窗口并发竞态条件(check-then-act),加 synchronized 保证原子性
- 修复:本地缓存内存泄漏,增加守护线程定期清理过期 Key
- 修复:rate-limit 模块 POM parent 版本写死,统一改为
${revision}
- 新增:接口限流模块(滑动窗口算法),支持注解 + YAML 双模式
- 新增:限流维度(GLOBAL / IP / USER)
- 新增:限流 Actuator 监控端点
- 防重复提交初始版本
- JDK 1.8+
- Spring Boot 2.7.x
- Redis 5.0+(使用 Redis 存储时)
Gitee 从 GitHub 自动同步,Issues 和 PR 请提交到 GitHub。
