## 什么是重放攻击(Replay Attacks)
`重放攻击`是一种黑客常用的攻击手段, 又称`重播攻击`、`回放攻击`, 是指攻击者发送目的主机`已接收过的数据`, 以达到欺骗系统的目的, 主要用于身份认证过程, 破坏认证的正确性.
![](https://img.kancloud.cn/0b/f4/0bf45b6b8776eab1ddebb90bf07999e0_700x480.png)
### HTTPS数据加密是否可以防止重放攻击?
否,加密可以有效防止明文数据被监听,但是却防止不了重放攻击。
## 如何防御重放攻击
1. `加随机数nonce`: 该方法优点是认证双方不需要时间同步,双方记住使用过的随机数, 如发现报文中有以前使用过的随机数, 就认为是重放攻击. 缺点是需要额外保存使用过的随机数, 若记录的时间段较长, 则保存和查询的开销较大.
2. `加时间戳timestamp`: 该方法优点是不用额外保存其他信息. 缺点是认证双方需要准确的时间同步, 同步越好, 受攻击的可能性就越小. 但当系统很庞大, 跨越的区域较广时, 要做到精确的时间同步并不是很容易.
3. `加流水号`: 就是双方在报文中添加一个逐步递增的整数, 只要接收到一个不连续的流水号报文(太大或太小), 就认定有重放威胁. 该方法优点是不需要时间同步, 保存的信息量比随机数方式小. 缺点是一旦攻击者对报文解密成功, 就可以获得流水号, 从而每次将流水号递增欺骗认证端.
在实际使用中, 常将1和2结合使用, 时间戳有效期内判断随机数是否已存在, 有效期外则直接丢弃.
## 使用timestamp和nonce
我们采取`时间戳`+`随机数`的方式来实现一个简单的重放攻击拦截器. 时间戳和随机数互补, 既能在时间有效范围内通过校验缓存中的随机数是否存在来分辨是否为重放请求, 也能在缓存失效后(缓存有效时间和时间范围一致)通过时间戳来校验该请求是否为重放. 如图:
![](https://img.kancloud.cn/15/ec/15ec4bf4a926907278f4dd457c6e546b_700x480.png)
```
@Resource
private ReactiveStringRedisTemplate reactiveStringRedisTemplate;
private ReactiveValueOperations<String, String> reactiveValueOperations;
@PostConstruct
public void postConstruct() {
reactiveValueOperations = reactiveStringRedisTemplate.opsForValue();
}
@Override
protected Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain) {
// 此处的<code>ATTRIBUTE_OPEN_API_REQUEST_BODY</code>是前面过滤器存入的
OpenApiRequest<String> body
= exchange.getRequiredAttribute(ATTRIBUTE_OPEN_API_REQUEST_BODY);
if (!ObjectUtils.allNotNull(body, body.getTimestamp(), body.getNonce())) {
return fail(exchange);
}
Long gmt = System.currentTimeMillis();
// (一)
if (gmt + effectiveTimeRange < body.getTimestamp() ||
gmt - effectiveTimeRange > body.getTimestamp()) {
return fail(exchange);
}
// (二)
return reactiveValueOperations.setIfAbsent(MessageFormat.format(
KEY_REPLAY_NONCE, body.getAppId(), body.getNonce()),
String.valueOf(System.currentTimeMillis()),
Duration.ofMillis(effectiveTimeRange * 2L))
.log(LOGGER, Level.FINE, true)
.flatMap(approved -> approved ?
chain.filter(exchange) : fail(FORBIDDEN, exchange)
);
```
* `(一)`: 请求时间超出时间范围的将被拒绝.
* `(二)`: 缓存过期时间等于有效时间的跨度, 若缓存中已存在该随机数, 则拒绝.
## 总结
* 记录请求标识并缓存, 接受请求时校验, 拒绝重放, 即将`nonce`存入缓存, 拒绝相同的`nonce`
* 随机数的方式可能造成过多的缓存, 故需要配合时间戳进行过滤, 时间戳不在有效范围内的一律拒绝.
重放攻击是一种常用且有效的攻击手段, 其危害不可忽视, 尽管可以通过业务层面来保障数据的正确性, 但依旧会给系统造成不必要开销, 在网关层过滤掉重放请求是一个不错的选择.
## 参考资料
> https://segmentfault.com/a/1190000020036097?utm_source=sf-related
https://segmentfault.com/a/1190000014821815