面试官:你讲下接口防重放如何处理?

· 浏览次数 : 0

小编点评

本文主要介绍了如何防重放攻击,以防止第三方服务/客户端恶意刷取API接口。 **一、何谓防重放攻击** 防重放攻击是指在网络交互过程中,客户端多次发送相同请求,导致服务端处理重复请求的问题。例如,当用户点赞后,H5会向掘金后端发送一个请求,若通过抓包工具复制请求并再次发送,就可能实现放重放攻击。 **二、掘金的防重放策略** 掘金通过以下方式实现了防重放攻击: 1. 服务端判断请求是否已处理:通过查询数据库(如item_id为唯一索引)来判断是否已经处理过点赞请求。 2. 时间戳+token验证:定义一个算法,包括加密时间戳和传入字段,如MD5。每次请求时,加上一个随机数(nonceKey)和时间戳,然后将token与数据库中的记录进行对比。 3. Redis去重:使用Redis存储一个随机数(nonceKey),设置30秒过期时间。若在30秒内收到相同的请求,认为重复请求,拒绝处理。 **三、算法实现与安全性分析** 具体的防重放算法实现包括以下几个部分: 1. 获取请求参数中的时间戳(timestamp)和token。 2. 根据salt、timestamp和参数名称生成一个token。 3. 后端接收请求,并使用相同的算法验证token是否匹配;如果匹配,则接受请求;否则,拒绝请求。 在实际场景中,可以使用JavaScript进行加密,同时加入salt以确保安全性。但需注意,防重放攻击的算法及加密方式容易被猜测,因此加入salt、随机数和时间戳等措施可以有效提高安全性。 **四、个人观点与建议** 我认为防重放攻击的作用有限,而更重要的是保证接口的非幂等性。例如,在支付和点赞等高危操作中使用数据库的唯一索引实现幂等性,可以有效防止攻击。 最后,面试中测试面试官对于防重放攻击的理解程度也是非常有意义的。

正文

前言

我们的API接口都是提供给第三方服务/客户端调用,所有请求地址以及请求参数都是暴露给用户的。

我们每次请求一个HTTP请求,用户都可以通过F12,或者抓包工具fd看到请求的URL链接,然后copy出来。这样是非常不安全的,有人可能会恶意的刷我们的接口,那这时该怎么办呢?防重放攻击就出来了。

什么是防重放攻击

我们以掘金文章点赞为例。当我点赞之后,H5会发送一个请求给到掘金后端服务器,我可以通过f12看到完整的请求参数,包括url,param等等,然后我可以通过copy把这个请求给copy出来,那么我就可以做到一个放重放攻击了。

具体如下。我们可以看到,服务端返回的是重复点赞,也就是掘金并没有做我们所谓世俗意义上的放重放攻击。掘金通过查询数据库(推测item_id是唯一索引值),来判断是否已经点赞然后返回前端逻辑。

那么什么是我们理解的放重放呢

简单来说就是,前端和客户端约定一个算法(比如md5),通过加密时间戳+传入字段。来起到防止重复请求的目的。
然后这个时间戳可以设定为30秒,60秒过期。

那么如果30秒,有人不断刷我们的接口怎么办。
我们还可以新加一个字段为nonceKey,30秒内随机不重复。这个字段存放在Redis,并且30秒过期。
如果下一次请求nonceKey还在redis,我就认为是重复请求,拒绝即可。

算法实现

  1. 首先定义一个全局拦截器
@Component
public class TokenInterceptor implements HandlerInterceptor {

	@Autowired
	private StringRedisTemplate redisService;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		String timestamp = request.getParameter("timestamp");
		String token = request.getParameter("token");
		if (timestamp == null || token == null) {
			return false;
		}

		TreeMap<String, String> map = new TreeMap<>();
		Enumeration<String> parameterNames = request.getParameterNames();
		while (parameterNames.hasMoreElements()) {
			String str = parameterNames.nextElement();
			if (StringUtils.equals(str, "token")) {
				continue;
			}
			map.put(str, request.getParameter(str));
		}

		return SecretUtils.extractSecret(redisService, timestamp, token, map);
	}
}
  1. 定义具体的算法实现
public class SecretUtils {

	private static final long NONCE_DURATION = 60 * 1000L;
	private static final String SALT = "salt"; // 注意这块加盐

	public static boolean extractSecret(StringRedisTemplate redisService, String timestamp, String token, TreeMap<String, String> map) {
		if (StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(token)) {
			return false;
		}
		long ts = NumberUtils.toLong(timestamp, 0);
		long now = System.currentTimeMillis();
		if ((now - ts) > SecretUtils.NONCE_DURATION || ts > now) {
			return false;
		}

		StringBuilder sb = new StringBuilder();
		map.put(SALT, SALT);
		for (Map.Entry<String, String> entry : map.entrySet()) {
			String key = entry.getKey();
			String value = entry.getValue();
			if (sb.length() > 0) {
				sb.append("&");
			}
			sb.append(key).append("=").append(value);
		}

		String targetToken = DigestUtils.md5DigestAsHex(sb.toString().getBytes());
		if (!token.equals(targetToken)) {
			return false;
		}

		String s = redisService.opsForValue().get(timestamp);
		if (StringUtils.isNotEmpty(s)) {
			return false;
		} else {
			redisService.opsForValue().set(timestamp, timestamp, NONCE_DURATION, TimeUnit.MILLISECONDS);
		}

		return true;
	}

}

前端会通过我们事先约定好的算法以及方式,将字符串从小到大进行排序 + timestamp,然后md5进行加密生成token传给后端。后端根据算法+方式来校验token是否有效。

如果其中有人修改了参数,那么token就会校验失败,直接拒绝即可。如果没修改参数,timestamp如果大于60s,则认为是防重放攻击,直接拒绝,如果小于30s,则将nonceKey加入到redis里面,这里nonceKey用的是timestamp字段,如果不存在则第一次请求,如果存在,则直接拒绝即可。

通过这么简单的一个算法,就可以实现防重放攻击了。

Q&A

Q:客户端和服务端生成的时间戳不一致怎么办

A:客户端和服务端生成的是时间戳,不是具体的时间,时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数

Q:HTTPS数据加密是否可以防止重放攻击

A:不可以。https是在传输过程中保证了加密,也就是说如果中间人,获取到了请求,他是无法解开传输的内容的。

举个最简单的例子,上课和同学传纸条的时候,为了不让中间给递纸条的人看到或者修改,可以在纸条上写成只有双方能看明白密文,这样递纸条的过程就安全了,传纸条过程中的人就看不懂你的内容了。但是如果给你写纸条的人要搞事情,那就是加密解决不了的了。这时候就需要放重放来解决了。

Q:防重放攻击是否有用,属于脱裤子放屁

A:个人感觉有一点点吧。比如防重放攻击的算法+加密方式其实大多数用的都是这些,其实攻击人很容易就能猜到token生成的方式,比如timestamp + 从小到大排序。因此我们加入了salt来混淆视听,这个salt需要前端、客户端安全的存储,不能让用户知道,比如js混淆等等。但其实通过抓包,js分析还是很容易能拿到的。但无形中增加了攻击人的成本,比如网易云登录的js加密类似。

Q:做了防重放,支付,点赞等是否不需要做幂等了

A:需要。最重要的幂等,一定要用数据库来实现,比如唯一索引。其他都不可相信。

最后

以我个人的理解。防重放用处不大,其他安全措施,比如非对称的RSA验签更加有效。就算用户拿到了请求的所有信息,你的接口也一定要做幂等的,尤其是像支付转账等高危操作,幂等才是最有用的防线。而且防重发生成token的算法,大家都这样搞,攻击者怎么可能不知道呢?这点我不太理解。

现在面试也比较考验面试官的水平,下篇我会讲下最近的一些面试体验和感受,欢迎大家点赞收藏。

与面试官:你讲下接口防重放如何处理?相似的内容:

面试官:你讲下接口防重放如何处理?

前言 我们的API接口都是提供给第三方服务/客户端调用,所有请求地址以及请求参数都是暴露给用户的。 我们每次请求一个HTTP请求,用户都可以通过F12,或者抓包工具fd看到请求的URL链接,然后copy出来。这样是非常不安全的,有人可能会恶意的刷我们的接口,那这时该怎么办呢?防重放攻击就出来了。 什

上周热点回顾(6.10-6.16)

热点随笔: · 「指间灵动,快码加编」:阿里云通义灵码,再次降临博客园 (博客园团队)· 老生常谈!程序员为什么要阅读源代码? (Yxh_blogs)· 千万级流量冲击下,如何保证极致性能 (Hello-Brand)· 面试官:你讲下接口防重放如何处理? (程序员博博)· C#开发的目录图标更改器

面试官:你讲下如何设计支持千万级别的短链?

前言 前几天面试遇到的,感觉比较有趣。第一次面试遇到考架构设计相关的题目,挺新奇的,开始向国外大厂靠拢了,比天天问八股文好太多了,工作5年左右的,问八股文,纯纯的不负责任偷懒行为。 感觉此问题比较有趣,这几天简单的实现了一版本,和大家分享一下具体的细节,也欢迎大家交流讨论, 代码github链接 s

上周面了百度,问的很细~

上周刚刚面了百度,问的问题不算很难,但却很细,我把这些面试题和答案都整理出来了,一起来看吧。 重点介绍一个你觉得有意义的项目? 回答技巧和思路: 介绍的项目业务难度和技术难点要高一些,最好是微服务项目。 简明扼要的讲清楚项目核心板块的业务场景即可,切忌不要讲的太细和太久,这只是面试官要考察你技术问题

能将三次握手讲到这个程度,不给你offer给谁!

摘要:在后端相关岗位的入职面试中,三次握手的出场频率非常的高,甚至说它是必考题也不为过。 本文分享自华为云社区《能将三次握手理解到这个深度,面试官拍案叫绝~》,作者:龙哥手记。 在后端相关岗位的入职面试中,三次握手的出场频率非常的高,甚至说它是必考题也不为过。一般的答案都是说客户端如何发起 SYN

面试官:Dubbo一次RPC请求经历哪些环节?

大家好,我是三友~~ 今天继续探秘系列,扒一扒一次RPC请求在Dubbo中经历的核心流程。 本文是基于Dubbo3.x版本进行讲解 一个简单的Demo 这里还是老样子,为了保证文章的完整性和连贯性,方便那些没有使用过的小伙伴更加容易接受文章的内容,这里快速讲一讲Dubbo一个简单的Demo 如果你已

一文讲尽Thread类的源码精髓

摘要:今天,我们就一起来简单看看Thread类的源码。 本文分享自华为云社区《【高并发】Thread类的源码精髓》,作者:冰 河。 前言 最近和一个朋友聊天,他跟我说起了他去XXX公司面试的情况,面试官的一个问题把他打懵了!竟然问他:你经常使用Thread创建线程,那你看过Thread类的源码吗?我

2024好用的项目管理软件有哪些?这10款最火国内项目管理工具你应该知道

不管是大公司还是小公司,如果想提高企业运作效率、规范管理并且高效且实用的项目管理工具,对项目流程进行把控、及时共享工作进度,从而让工作变得更有效率。那么一款好用的项目管理工具必不可少。然而面对市场上这么多的项目管理工具,你是否感到疑惑,不知道选择哪款项目管理软件好?那么在本文中我们挑选了10款最优秀

终于搞懂了!原来 Vue 3 的 generate 是这样生成 render 函数的

前言 在之前的 面试官:来说说vue3是怎么处理内置的v-for、v-model等指令? 文章中讲了transform阶段处理完v-for、v-model等指令后,会生成一棵javascript AST抽象语法树。这篇文章我们来接着讲generate阶段是如何根据这棵javascript AST抽象

Java多线程-JUC-1(八)

前面把线程相关的生命周期、关键字、线程池(ThreadPool)、ThreadLocal、CAS、锁和AQS都讲完了,现在就剩下怎么来用多线程了。而要想用好多线程,其实是可以取一些巧的,比如JUC(好多面试官喜欢问的JUC,就是现在要讲的JUC)。JUC就是java.util.concurrent的