SpringBoot3基于 Sa-Token 實現(xiàn) API 接口簽名校驗實戰(zhàn)
在微服務架構中,系統(tǒng)之間的調(diào)用往往需要保證 安全性。如果缺乏有效的防護機制,接口極易遭受偽造請求攻擊。
接口簽名校驗 就是一種常見的安全手段,可以有效避免參數(shù)篡改、重放攻擊。
本文將基于 Spring Boot 3 + Sa-Token 的 sa-token-sign 模塊,手把手帶你實現(xiàn)接口簽名校驗,并擴展到數(shù)據(jù)庫存儲,支持動態(tài)接入。
簽名校驗流程圖
Client Server
| |
| appid, params, sign, timestamp, nonce |
| -----------------------------------> |
| | 1. 從數(shù)據(jù)庫加載密鑰配置
| | 2. 驗證 timestamp 是否在有效期內(nèi)
| | 3. 驗證 nonce 是否重復
| | 4. 計算簽名并比對
| | 5. Redis 緩存配置(12小時)
| <----------------------------------|
| Response |
Sa-Token 簽名模塊簡介
sa-token-sign 模塊開箱即用,提供了:
? 支持 MD5 / SHA256 / SHA512
? 內(nèi)置 timestamp / nonce 校驗
? 支持 多應用配置
? 提供 @SaCheckSign 注解,零侵入接入
? 支持 自定義配置源(數(shù)據(jù)庫)
項目依賴
<!-- Sa-Token Starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.44.0</version>
</dependency>
<!-- API 參數(shù)簽名 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sign</artifactId>
<version>1.44.0</version>
</dependency>
<!-- Sa-Token 與 Redis 集成(用于緩存簽名配置) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>1.44.0</version>
</dependency>
數(shù)據(jù)庫設計
1 表結(jié)構
create table t_app_sign_config
(
id bigint auto_increment comment '主鍵ID' primary key,
app_id varchar(64) not null comment '應用ID',
secret_key varchar(128) not null comment '密鑰',
digest_algo varchar(32) default 'md5' not null comment '簽名算法: md5 / sha256 / sha512',
timestamp_disparity bigint default 900000 null comment '時間戳允許誤差(毫秒) 默認15分鐘',
create_by varchar(50) null comment '創(chuàng)建人',
create_time datetime default CURRENT_TIMESTAMP null comment '創(chuàng)建時間',
update_by varchar(50) null comment '更新人',
update_time datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新時間',
constraint uk_app_id unique (app_id)
) comment '應用簽名配置表';
2 初始化數(shù)據(jù)
INSERT INTO t_app_sign_config (app_id, secret_key, digest_algo, timestamp_disparity, create_by)
VALUES ('AppId1', '601b8ddd3037c782476e4be8102f6a07', 'md5', 900000, 'admin');
INSERT INTO t_app_sign_config (app_id, secret_key, digest_algo, timestamp_disparity, create_by)
VALUES ('AppId2', '954911e93f7e14fe1e09a713bf96b0da', 'md5', 900000, 'admin');
后端實現(xiàn)
1 實體類
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("t_app_sign_config")
public class AppSignConfig extends BaseEntity {
/**
* 應用ID
*/
private String appId;
/**
* 密鑰
*/
private String secretKey;
/**
* md5 / sha256 / sha512
*/
private String digestAlgo;
/**
* 時間戳誤差(秒)
*/
private Long timestampDisparity;
}
2 Service 層(含 Redis 緩存)
public interface IAppSignConfigService extends IService<AppSignConfig> {
AppSignConfig getByAppId(String appId);
}
@Service
public class AppSignConfigServiceImpl extends ServiceImpl<AppSignConfigRepository, AppSignConfig>
implements IAppSignConfigService {
private static final String CACHE_PREFIX = "rbom-sync:api:signconfig:";
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
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 appSignConfig = lambdaQuery()
.eq(AppSignConfig::getAppId, appId)
.one();
// 緩存配置(12小時)
redisTemplate.opsForValue()
.set(cacheKey, JsonUtils.toJson(appSignConfig), Duration.ofHours(12));
return appSignConfig;
}
}
3 自定義簽名配置加載器
為什么能夠動態(tài)加載簽名信息,這一步很重要
@Component
public class MySignConfigLoader {
private static final Logger logger = LoggerFactory.getLogger(MySignConfigLoader.class);
@Autowired
private IAppSignConfigService appSignConfigService;
@PostConstruct
public void init() {
logger.info("初始化自定義簽名配置加載器...");
// 覆蓋 SaSignMany 的查找邏輯
SaSignMany.findSaSignConfigMethod = (appid) -> {
try {
logger.debug("查找應用簽名配置,appid: {}", appid);
AppSignConfig appSignConfig = appSignConfigService.getByAppId(appid);
if (appSignConfig == null) {
logger.warn("未找到應用簽名配置,appid: {}", appid);
throw new RuntimeException("appid 不存在: " + appid);
}
SaSignConfig config = new SaSignConfig();
config.setSecretKey(appSignConfig.getSecretKey());
config.setDigestAlgo(appSignConfig.getDigestAlgo());
config.setTimestampDisparity(appSignConfig.getTimestampDisparity());
logger.debug("成功加載應用簽名配置,appid: {}, algorithm: {}",
appid, appSignConfig.getDigestAlgo());
return config;
} catch (Exception e) {
logger.error("加載應用簽名配置失敗,appid: {}, error: {}", appid, e.getMessage(), e);
throw new BusinessException("加載應用簽名配置失敗: " + e.getMessage(), e);
}
};
logger.info("自定義簽名配置加載器初始化完成");
}
}
4 攔截器配置
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 開啟 Sa-Token 注解攔截
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
5 Controller 使用
@RestController
@RequestMapping("/sync")
public class SyncController {
@Autowired
private IModelService modelService;
// 從請求參數(shù)中動態(tài)獲取 appid
@SaCheckSign(appid = "#{appid}")
@GetMapping("/model")
public ResponseEntity<Res> model() {
List<Model> models = modelService.list();
return ResponseEntity.ok(Res.success(models));
}
}
6 application.properties 配置
# Sa-Token 配置 sa-token.token-name=***-sync:satoken
客戶端調(diào)用示例
1 Java 客戶端(OkHttp)
OkHttpClient client = new OkHttpClient().newBuilder().build();
String appid = "AppId1";
String nonce = UUID.randomUUID().toString();
long timestamp = System.currentTimeMillis();
// 拼接簽名字符串
String raw = "appid=" + appid + "&nonce=" + nonce + "×tamp=" + timestamp
+ "&key=601b8ddd3037c782476e4be8102f6a07";
// 生成 MD5 簽名
String sign = DigestUtils.md5DigestAsHex(raw.getBytes(StandardCharsets.UTF_8));
// 發(fā)送請求
Request request = new Request.Builder()
.url("http://***:8080/sync/model"
+ "?appid=" + appid
+ "&sign=" + sign
+ "&nonce=" + nonce
+ "×tamp=" + timestamp)
.get()
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
6.2 curl 示例
curl "http://***:8080/sync/model?appid=MDM&sign=***&nonce=***×tamp=***"
架構設計亮點
| 設計點 | 說明 |
|---|---|
| 數(shù)據(jù)庫存儲 | 密鑰配置持久化,支持動態(tài)擴展新應用 |
| Redis 緩存 | 緩存簽名配置 12 小時,減少數(shù)據(jù)庫查詢 |
| 自定義加載器 | 通過 @PostConstruct 覆蓋默認配置加載邏輯 |
| 動態(tài) appid | 使用 SpEL 表達式 #{appid} 從請求參數(shù)獲取 |
| 異常處理 | 統(tǒng)一的日志記錄和異常封裝 |
| 唯一約束 | 數(shù)據(jù)庫 app_id 唯一索引防止重復 |
常見問題
Q1: 如何添加新的應用?
直接在數(shù)據(jù)庫 t_app_sign_config 表插入新記錄即可,無需重啟服務。
Q2: 緩存失效后如何刷新?
緩存 TTL 為 12 小時,過期后自動從數(shù)據(jù)庫重新加載。
Q3: timestamp_disparity 的含義?
表示時間戳允許的誤差范圍(毫秒),默認 900000ms(15 分鐘),防止重放攻擊。
到此這篇關于SpringBoot3基于 Sa-Token 實現(xiàn) API 接口簽名校驗實戰(zhàn)的文章就介紹到這了,更多相關SpringBoot API接口簽名校驗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
RocketMq同組消費者如何自動設置InstanceName
這篇文章主要介紹了RocketMq同組消費者如何自動設置InstanceName問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
springboot單獨使用feign簡化接口調(diào)用方式
這篇文章主要介紹了springboot單獨使用feign簡化接口調(diào)用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Java注解如何基于Redission實現(xiàn)分布式鎖
這篇文章主要介紹了Java注解如何基于Redission實現(xiàn)分布式鎖,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01
SpringBoot 項目中的圖片處理策略之本地存儲與路徑映射
在SpringBoot項目中,靜態(tài)資源存放在static目錄下,使得前端可以通過URL來訪問這些資源,我們就需要將文件系統(tǒng)的文件路徑與URL建立一個映射關系,把文件系統(tǒng)中的文件當成我們的靜態(tài)資源即可,本文給大家介紹SpringBoot本地存儲與路徑映射的相關知識,感興趣的朋友一起看看吧2023-12-12

