最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot3基于 Sa-Token 實(shí)現(xiàn) API 接口簽名校驗(yàn)實(shí)戰(zhàn)

 更新時(shí)間:2025年12月26日 08:25:13   作者:九月生  
本文介紹了基于Spring Boot 3和Sa-Token框架實(shí)現(xiàn)API接口簽名校驗(yàn)的解決方案,通過(guò)sa-token-sign模塊提供開(kāi)箱即用的簽名校驗(yàn)功能,下面就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下

在微服務(wù)架構(gòu)中,系統(tǒng)之間的調(diào)用往往需要保證 安全性。如果缺乏有效的防護(hù)機(jī)制,接口極易遭受偽造請(qǐng)求攻擊。
接口簽名校驗(yàn) 就是一種常見(jiàn)的安全手段,可以有效避免參數(shù)篡改、重放攻擊。
本文將基于 Spring Boot 3 + Sa-Tokensa-token-sign 模塊,手把手帶你實(shí)現(xiàn)接口簽名校驗(yàn),并擴(kuò)展到數(shù)據(jù)庫(kù)存儲(chǔ),支持動(dòng)態(tài)接入。

簽名校驗(yàn)流程圖

   Client                              Server
      |                                    |
      | appid, params, sign, timestamp, nonce |
      | -----------------------------------> |
      |                                    | 1. 從數(shù)據(jù)庫(kù)加載密鑰配置
      |                                    | 2. 驗(yàn)證 timestamp 是否在有效期內(nèi)
      |                                    | 3. 驗(yàn)證 nonce 是否重復(fù)
      |                                    | 4. 計(jì)算簽名并比對(duì)
      |                                    | 5. Redis 緩存配置(12小時(shí))
      | <----------------------------------|
      |           Response                 |

Sa-Token 簽名模塊簡(jiǎn)介

sa-token-sign 模塊開(kāi)箱即用,提供了:

? 支持 MD5 / SHA256 / SHA512

? 內(nèi)置 timestamp / nonce 校驗(yàn)

? 支持 多應(yīng)用配置

? 提供 @SaCheckSign 注解,零侵入接入

? 支持 自定義配置源(數(shù)據(jù)庫(kù))

項(xiàng)目依賴

<!-- 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ù)庫(kù)設(shè)計(jì)

1 表結(jié)構(gòu)

create table t_app_sign_config
(
    id                  bigint auto_increment comment '主鍵ID' primary key,
    app_id              varchar(64)  not null comment '應(yīng)用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 '時(shí)間戳允許誤差(毫秒) 默認(rèn)15分鐘',
    create_by           varchar(50)  null comment '創(chuàng)建人',
    create_time         datetime     default CURRENT_TIMESTAMP null comment '創(chuàng)建時(shí)間',
    update_by           varchar(50)  null comment '更新人',
    update_time         datetime     default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新時(shí)間',
    constraint uk_app_id unique (app_id)
) comment '應(yīng)用簽名配置表';

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');

后端實(shí)現(xiàn)

1 實(shí)體類(lèi)

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("t_app_sign_config")
public class AppSignConfig extends BaseEntity {

    /**
     * 應(yīng)用ID
     */
    private String appId;

    /**
     * 密鑰
     */
    private String secretKey;

    /**
     * md5 / sha256 / sha512
     */
    private String digestAlgo;

    /**
     * 時(shí)間戳誤差(秒)
     */
    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小時(shí))
        redisTemplate.opsForValue()
                .set(cacheKey, JsonUtils.toJson(appSignConfig), Duration.ofHours(12));
        return appSignConfig;
    }
}

3 自定義簽名配置加載器

為什么能夠動(dòng)態(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("查找應(yīng)用簽名配置,appid: {}", appid);
                AppSignConfig appSignConfig = appSignConfigService.getByAppId(appid);

                if (appSignConfig == null) {
                    logger.warn("未找到應(yīng)用簽名配置,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("成功加載應(yīng)用簽名配置,appid: {}, algorithm: {}",
                        appid, appSignConfig.getDigestAlgo());
                return config;
            } catch (Exception e) {
                logger.error("加載應(yīng)用簽名配置失敗,appid: {}, error: {}", appid, e.getMessage(), e);
                throw new BusinessException("加載應(yīng)用簽名配置失敗: " + e.getMessage(), e);
            }
        };
        logger.info("自定義簽名配置加載器初始化完成");
    }
}

4 攔截器配置

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 開(kāi)啟 Sa-Token 注解攔截
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
    }
}

5 Controller 使用

@RestController
@RequestMapping("/sync")
public class SyncController {

    @Autowired
    private IModelService modelService;

    // 從請(qǐng)求參數(shù)中動(dòng)態(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 + "&timestamp=" + timestamp
             + "&key=601b8ddd3037c782476e4be8102f6a07";
// 生成 MD5 簽名
String sign = DigestUtils.md5DigestAsHex(raw.getBytes(StandardCharsets.UTF_8));

// 發(fā)送請(qǐng)求
Request request = new Request.Builder()
        .url("http://***:8080/sync/model"
             + "?appid=" + appid
             + "&sign=" + sign
             + "&nonce=" + nonce
             + "&timestamp=" + 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=***&timestamp=***"

架構(gòu)設(shè)計(jì)亮點(diǎn)

設(shè)計(jì)點(diǎn)說(shuō)明
數(shù)據(jù)庫(kù)存儲(chǔ)密鑰配置持久化,支持動(dòng)態(tài)擴(kuò)展新應(yīng)用
Redis 緩存緩存簽名配置 12 小時(shí),減少數(shù)據(jù)庫(kù)查詢
自定義加載器通過(guò) @PostConstruct 覆蓋默認(rèn)配置加載邏輯
動(dòng)態(tài) appid使用 SpEL 表達(dá)式 #{appid} 從請(qǐng)求參數(shù)獲取
異常處理統(tǒng)一的日志記錄和異常封裝
唯一約束數(shù)據(jù)庫(kù) app_id 唯一索引防止重復(fù)

常見(jiàn)問(wèn)題

Q1: 如何添加新的應(yīng)用?

直接在數(shù)據(jù)庫(kù) t_app_sign_config 表插入新記錄即可,無(wú)需重啟服務(wù)。

Q2: 緩存失效后如何刷新?

緩存 TTL 為 12 小時(shí),過(guò)期后自動(dòng)從數(shù)據(jù)庫(kù)重新加載。

Q3: timestamp_disparity 的含義?

表示時(shí)間戳允許的誤差范圍(毫秒),默認(rèn) 900000ms(15 分鐘),防止重放攻擊。

到此這篇關(guān)于SpringBoot3基于 Sa-Token 實(shí)現(xiàn) API 接口簽名校驗(yàn)實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)SpringBoot API接口簽名校驗(yàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java基于redis實(shí)現(xiàn)分布式鎖代碼實(shí)例

    Java基于redis實(shí)現(xiàn)分布式鎖代碼實(shí)例

    這篇文章主要介紹了Java基于redis實(shí)現(xiàn)分布式鎖代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 程序員最喜歡的ThreadLocal使用姿勢(shì)

    程序員最喜歡的ThreadLocal使用姿勢(shì)

    ThreadLocal并不是一個(gè)Thread,而是Thread的局部變量,也許把它命名為T(mén)hreadLocalVariable更容易讓人理解一些,下面這篇文章主要給大家介紹了程序員最喜歡的ThreadLocal使用姿勢(shì),需要的朋友可以參考下
    2022-02-02
  • 微服務(wù)和分布式的區(qū)別詳解

    微服務(wù)和分布式的區(qū)別詳解

    在本篇文章里小編給各位整理了關(guān)于微服務(wù)和分布式的區(qū)別以及相關(guān)知識(shí)點(diǎn)總結(jié),有興趣的朋友們學(xué)習(xí)下。
    2019-07-07
  • Java中八種基本數(shù)據(jù)類(lèi)型的默認(rèn)值

    Java中八種基本數(shù)據(jù)類(lèi)型的默認(rèn)值

    這篇文章主要介紹了Java中八種基本數(shù)據(jù)類(lèi)型的默認(rèn)值 的相關(guān)資料,需要的朋友可以參考下
    2016-07-07
  • RocketMq同組消費(fèi)者如何自動(dòng)設(shè)置InstanceName

    RocketMq同組消費(fèi)者如何自動(dòng)設(shè)置InstanceName

    這篇文章主要介紹了RocketMq同組消費(fèi)者如何自動(dòng)設(shè)置InstanceName問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • 淺析Java內(nèi)存模型與垃圾回收

    淺析Java內(nèi)存模型與垃圾回收

    下面小編就為大家?guī)?lái)一篇淺析Java內(nèi)存模型與垃圾回收。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,祝大家游戲愉快哦
    2016-05-05
  • springboot單獨(dú)使用feign簡(jiǎn)化接口調(diào)用方式

    springboot單獨(dú)使用feign簡(jiǎn)化接口調(diào)用方式

    這篇文章主要介紹了springboot單獨(dú)使用feign簡(jiǎn)化接口調(diào)用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • vue3實(shí)現(xiàn)一個(gè)todo-list

    vue3實(shí)現(xiàn)一個(gè)todo-list

    這篇文章主要為大家詳細(xì)介紹了基于vuejs實(shí)現(xiàn)一個(gè)todolist項(xiàng)目,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能給你帶來(lái)幫助
    2021-08-08
  • Java注解如何基于Redission實(shí)現(xiàn)分布式鎖

    Java注解如何基于Redission實(shí)現(xiàn)分布式鎖

    這篇文章主要介紹了Java注解如何基于Redission實(shí)現(xiàn)分布式鎖,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • SpringBoot 項(xiàng)目中的圖片處理策略之本地存儲(chǔ)與路徑映射

    SpringBoot 項(xiàng)目中的圖片處理策略之本地存儲(chǔ)與路徑映射

    在SpringBoot項(xiàng)目中,靜態(tài)資源存放在static目錄下,使得前端可以通過(guò)URL來(lái)訪問(wèn)這些資源,我們就需要將文件系統(tǒng)的文件路徑與URL建立一個(gè)映射關(guān)系,把文件系統(tǒng)中的文件當(dāng)成我們的靜態(tài)資源即可,本文給大家介紹SpringBoot本地存儲(chǔ)與路徑映射的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2023-12-12

最新評(píng)論

恭城| 莱阳市| 建德市| 尖扎县| 罗源县| 益阳市| 通州市| 绥中县| 合作市| 贵阳市| 彭阳县| 丰原市| 长宁区| 新乡县| 攀枝花市| 永登县| 保定市| 兴文县| 广昌县| 前郭尔| 年辖:市辖区| 正定县| 长沙县| 潼南县| 盖州市| 洪洞县| 海盐县| 芒康县| 仙桃市| 锡林郭勒盟| 高清| 苍溪县| 柘城县| 金塔县| 阳原县| 宁河县| 凯里市| 高碑店市| 闻喜县| 英超| 秦安县|