基于 Sa-Token 实现 API 接口签名校验(Spring Boot 3 实战)
🚀 基于 Sa-Token 实现 API 接口签名校验(Spring Boot 3 实战)
在微服务架构中,系统之间的调用往往需要保证 安全性。如果缺乏有效的防护机制,接口极易遭受伪造请求攻击。
👉 接口签名校验 就是一种常见的安全手段,可以有效避免参数篡改、重放攻击。
本文将基于 Spring Boot 3 + Sa-Token 的 sa-token-sign 模块,手把手带你实现接口签名校验,并扩展到数据库存储,支持动态接入。
1️⃣ 签名校验原理
签名流程大致如下:
- 客户端:将
appid + nonce + timestamp + secret拼接后生成sign - 服务端:根据
appId找到对应的secret,再生成一次签名并比对 - 同时校验:
- timestamp 是否过期(防止超时请求)
- nonce 是否已使用(防止重放攻击)
📊 流程图:
Client Server
| |
| appId, params, sign | --> 验签(Secret、timestamp、nonce)
| |
| <----- Response ----|
2️⃣ Sa-Token 签名模块简介
sa-token-sign 模块开箱即用,提供了:
✅ 支持 MD5 / SHA256 / SHA512
✅ 内置 timestamp / nonce 校验
✅ 支持 多应用配置(配置文件 or 数据库)
✅ 提供 @SaCheckSign 注解,零侵入接入
3️⃣ 配置文件模式(入门最快)
3.1 引入依赖
<!-- Sa-Token Starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.44.0</version>
</dependency>
<!-- API 参数签名 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sign</artifactId>
<version>1.44.0</version>
</dependency>
3.2 application.properties 配置
# 应用1
sa-token.sign-many.MDM.secret-key=1044ebd2843c1a9c4b9900135e706ba8
sa-token.sign-many.MDM.digest-algo=md5
# 应用2
sa-token.sign-many.forum.secret-key=abcdefg123456
sa-token.sign-many.forum.digest-algo=sha256
3.3 配置拦截器
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 开启 Sa-Token 注解拦截
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
3.4 控制器示例
@RestController
@RequestMapping("/api/shop")
public class ShopController {
// 只允许 appid=MDM 的请求访问
@SaCheckSign(appid = "MDM")
@GetMapping("/data")
public Object getShopData() {
return Map.of("status", "ok", "msg", "shop data success");
}
}
3.5 请求示例
Java 客户端
OkHttpClient client = new OkHttpClient().newBuilder().build();
String appid = "MDM";
String nonce = UUID.randomUUID().toString();
long timestamp = System.currentTimeMillis();
// 拼接签名字符串
String raw = "appid=" + appid + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=1044ebd2843c1a9c4b9900135e706ba8";
// 生成 MD5 签名
String sign = DigestUtils.md5DigestAsHex(raw.getBytes(StandardCharsets.UTF_8));
// 发送请求
Request request = new Request.Builder()
.url("http://localhost:8080/api/shop/data"
+ "?appid=" + appid
+ "&sign=" + sign
+ "&nonce=" + nonce
+ "×tamp=" + timestamp)
.get()
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
curl 示例
curl "http://localhost:8080/api/shop/data?appid=MDM&sign=97c520a7...&nonce=c6ee2098-...×tamp=1758695980450"
💡 优点:简单易用
⚠️ 缺点:修改配置必须重启服务
4️⃣ 数据库存储(推荐方案)
当调用方数量较多时,推荐把签名信息放入数据库,支持动态扩展。
4.1 表结构
create table t_app_sign_config (
id bigint auto_increment primary key,
app_id varchar(64) not null unique comment '应用ID',
secret_key varchar(128) not null comment '密钥',
digest_algo varchar(32) default 'md5' not null comment '签名算法',
timestamp_disparity bigint default 900000 comment '时间戳误差(毫秒)',
create_time datetime default CURRENT_TIMESTAMP,
update_time datetime default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
);
4.2 实体类
@Data
@TableName("t_app_sign_config")
public class AppSignConfig {
@TableId(type = IdType.AUTO)
private Long id;
private String appId;
private String secretKey;
private String digestAlgo;
private Long timestampDisparity;
}
4.3 Service + Redis 缓存
@Service
public class AppSignConfigServiceImpl extends ServiceImpl<AppSignConfigRepository, AppSignConfig> {
private static final String CACHE_PREFIX = "demo:api:signconfig:";
@Autowired
private RedisTemplate<String, String> redisTemplate;
public AppSignConfig getByAppId(String appId) {
String cacheKey = CACHE_PREFIX + appId;
String cache = redisTemplate.opsForValue().get(cacheKey);
if (cache != null) {
return JsonUtils.fromJson(cache, AppSignConfig.class);
}
AppSignConfig config = lambdaQuery().eq(AppSignConfig::getAppId, appId).one();
redisTemplate.opsForValue().set(cacheKey, JsonUtils.toJson(config), Duration.ofHours(12));
return config;
}
}
4.4 自定义配置加载器
@Component
public class MySignConfigLoader {
@Autowired
private AppSignConfigServiceImpl appSignConfigService;
@PostConstruct
public void init() {
// 覆盖 Sa-Token 默认加载逻辑
SaSignMany.findSaSignConfigMethod = (appid) -> {
AppSignConfig cfg = appSignConfigService.getByAppId(appid);
if (cfg == null) throw new RuntimeException("appid 不存在: " + appid);
SaSignConfig config = new SaSignConfig();
config.setSecretKey(cfg.getSecretKey());
config.setDigestAlgo(cfg.getDigestAlgo());
config.setTimestampDisparity(cfg.getTimestampDisparity());
return config;
};
}
}
5️⃣ Redis 记录 nonce 防重放
只需引入一个依赖即可:
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>1.44.0</version>
</dependency>
6️⃣ 两种方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 配置文件 | 简单,快速接入 | 修改需重启服务 | 小规模,调用方固定 |
| 数据库 | 动态扩展,易管理 | 增加 DB/缓存依赖 | 多应用,大规模接入 |
7️⃣ 最佳实践建议
- Redis 缓存:减少 DB 压力
- 开启 nonce 校验:避免重放攻击
- 日志监控:记录失败原因(签名错误、超时、nonce 重复)
- 灰度迁移:配置文件模式 → 数据库存储
🎯 总结
sa-token-sign模块让接口签名校验变得轻量易用- 配置文件适合小规模场景,数据库模式适合大规模、多调用方接入
- 搭配 Redis + 日志监控,可以快速构建企业级 API 防护体系
版权声明:
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自
LibSept24_!
喜欢就支持一下吧
打赏
微信
支付宝
微信
支付宝