基于缓存注解的时间戳令牌防重复提交设计

embedded/2024/10/22 10:50:35/

文章目录

  • 一,概述
  • 二,实现过程
    • 1、引入pom依赖
    • 2、定义缓存管理
    • 3、时间戳服务类
    • 4、模拟测试接口
  • 三,测试过程
    • 1, 模拟批量获取
    • 2, 消费令牌
  • 四,源码放送
  • 五,优化方向

一,概述

API接口由于需要供第三方服务调用,所以必须暴露到外网,并提供了具体请求地址和请求参数。为了防止重放攻击必须要保证请求仅一次有效

比较成熟的做法有批量颁发时间戳令牌,每次请求消费一个令牌

二,实现过程

下面我们基于本地缓存caffeine来说明具体实现。

1、引入pom依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!-- caffeine依赖 --><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId></dependency>

2、定义缓存管理


import java.util.concurrent.TimeUnit;import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import com.github.benmanes.caffeine.cache.Caffeine;/*** * CacheConfig* * @author 00fly* @version [版本号, 2019年12月18日]* @see [相关类/方法]* @since [产品/模块版本]*/
@Configuration
public class CacheConfig extends CachingConfigurerSupport
{@Bean@Overridepublic CacheManager cacheManager(){CaffeineCacheManager cacheManager = new CaffeineCacheManager();// 方案一(常用):定制化缓存CachecacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).initialCapacity(100).maximumSize(10000));// 如果缓存种没有对应的value,通过createExpensiveGraph方法同步加载 buildAsync是异步加载// .build(key -> createExpensiveGraph(key))// 方案二:传入一个CaffeineSpec定制缓存,它的好处是可以把配置方便写在配置文件里// cacheManager.setCaffeineSpec(CaffeineSpec.parse("initialCapacity=50,maximumSize=500,expireAfterWrite=5s"));return cacheManager;}
}

3、时间戳服务类

注意:一定要理解为什么使用SpringContextUtils


import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.LongStream;import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import com.fly.core.utils.SpringContextUtils;/*** TimestampService*/
@Service
public class TimestampService
{/*** 批量获取用户timestamp,支持缓存*/@Cacheable(value = "timestamp", key = "#user", unless = "#result.size()==0")public List<Long> batchGet(String user){String userId = DigestUtils.md5DigestAsHex(user.getBytes(StandardCharsets.UTF_8));if (StringUtils.isBlank(userId)){throw new RuntimeException("用户不存在");}return LongStream.range(0, 10).map(i -> System.currentTimeMillis() + i).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);}/*** 判断用户timestamp是否有效*/public boolean isFirstUse(String user, Long timestamp){// 注意:缓存基于代理实现,直接调用,缓存机制会失效TimestampService timestampService = SpringContextUtils.getBean(TimestampService.class);List<Long> data = timestampService.batchGet(user);boolean isFirstUse = data.contains(timestamp);if (isFirstUse){timestampService.removeThenUpdate(user, timestamp);}return isFirstUse;}/*** 移除用户已使用的timestamp,刷新缓存* */@CachePut(value = "timestamp", key = "#user")public List<Long> removeThenUpdate(String user, Long timestamp){// 注意:缓存基于代理实现,直接调用,缓存机制会失效TimestampService timestampService = SpringContextUtils.getBean(TimestampService.class);List<Long> data = timestampService.batchGet(user);data.remove(timestamp);if (data.size() < 5) // 及时补充{data.addAll(batchGet(user));}return data;}
}

4、模拟测试接口


import java.util.Collections;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import com.fly.core.entity.JsonResult;
import com.fly.openapi.service.TimestampService;import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;@Slf4j
@Api(tags = "接口辅助")
@RestController
@RequestMapping("/auto/help")
public class AutoHelpController
{@AutowiredTimestampService timestampService;@ApiOperation("批量获取用户timestamp")@GetMapping("/getBatchTimestamps")public JsonResult<?> getBatchTimestamps(@RequestParam String user){log.info("getBatchTimestamps for {}", user);return JsonResult.success(Collections.singletonMap("timestamps", timestampService.batchGet(user)));}@ApiOperation("消费timestamp")@GetMapping("/useTimestamp")public JsonResult<?> useTimestamp(@RequestParam String user, Long timestamp){log.info("useTimestamp for {}", user);return JsonResult.success(Collections.singletonMap("isFirstUse", timestampService.isFirstUse(user, timestamp)));}
}

三,测试过程

1, 模拟批量获取

输入用户名00fly
在这里插入图片描述

2, 消费令牌

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四,源码放送

https://gitcode.com/00fly/springboot-openapi

git clone https://gitcode.com/00fly/springboot-openapi.git

五,优化方向

  1. 批量获取令牌可采用https、tcp、grpc等更加安全的协议获的
  2. 获取令牌可以考虑采用非对称加密算法鉴权
  3. 多实例部署,可切换到分布式缓存,如redis

有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!

-over-


http://www.ppmy.cn/embedded/30656.html

相关文章

C++Day6作业

1、模板实现顺序栈 #include <iostream>using namespace std;template <typename T> class Stack {T* S; //数组int size;//栈容量int top; //栈顶元素下标 public://有参构造Stack(int size):S(new T[size]),size(size),top(-1){}//初始化时直接将栈顶元素下标设…

【快速入门Linux】10_Linux命令—Vi编辑器

文章目录 一、vi 简介1.1 vi1.2 vim1.3查询软连接命令&#xff08;知道&#xff09; 二、打开和新建文件&#xff08;重点&#xff09;2.1 打开文件并且定位行2.2 异常处理 三、vi三种工作模式&#xff08;重点&#xff09;3.1 末行模式-命令 四、常用命令4.0 命令线路图4.1 移…

线上线下包搭建小程序/公众号/H5 支持二开!

网上交友有以下三个积极影响&#xff1a; 1. 扩展社交圈和增加社交机会&#xff1a;网上交友可以让人们接触到不同地区、不同背景、不同文化的人&#xff0c;拓展人们的社交圈并且增加交友机会。这些新的社交联系对于个人的成长和发展有积极的影响&#xff0c;可以让人们学习新…

55.基于SpringBoot + Vue实现的前后端分离-旅游管理系统(项目 + 论文)

项目介绍 本站是一个B/S模式系统&#xff0c;采用SpringBoot Vue框架&#xff0c;MYSQL数据库设计开发&#xff0c;充分保证系统的稳定性。系统具有界面清晰、操作简单&#xff0c;功能齐全的特点&#xff0c;使得基于SpringBoot Vue技术的旅游管理系统设计与实现管理工作系统…

5.3作业

1.int (*s[10])(int)表示s是一个数组&#xff0c;含有10个元素&#xff0c;每个元素都是一个函数指针&#xff0c;参数为int&#xff0c;返回值为int。 2.&#xff08;1&#xff09;C (2)A (3)C (4)E,D (5)B 3. 7,10 4,(1)quelenm (2)quelen0 (3)队头位置(rear−quelen1m)%…

C++中的位运算符

位运算符&#xff1a; 按位与 (&) int a 6; // 0110 in binary int b 3; // 0011 in binary int c a & b; // 结果为 2 (0010)&#xff0c;因为对应位置都为1才为1 按位或 (|) int a 6; int b 3; int c a | b; // 结果为 7 (0111)&#xff0c;因为任一位置有1则…

停止使用 TypeScript 接口

为什么应该使用类型而不是接口 这张图片是由人工智能生成的。 类型和接口 是每个 TypeScript 程序中使用的重要特性。 然而&#xff0c;由于类型和接口在功能上非常相似&#xff0c;这就引出了一个问题&#xff1a;哪个更好&#xff1f; 今天&#xff0c;我们将评估类型和接…

YOLOv5白皮书-第Y6周:模型改进

YOLOv5白皮书-第Y6周&#xff1a;模型改进 YOLOv5白皮书-第Y6周&#xff1a;模型改进一、前言二、我的环境三、更正后的yolov5s.yaml四、运行截图![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/23c3ac6b05d74bfcbea5ec238681710d.png)五、总结 YOLOv5白皮书-第Y6周…