Spring Boot 防重放攻击原理及代码实现
Spring Boot Security About 4,835 words重放攻击
API
重放攻击(Replay Attacks
)又称重播攻击、回放攻击,这种攻击会不断恶意或欺诈性地重复一个有效的API
请求。攻击者利用网络监听或者其他方式盗取API
请求,进行一定的处理后,再把它重新发给认证服务器,是黑客常用的攻击方式之一。
防止重放攻击
使用签名之后,可以对请求的身份进行验证。但不能阻止重放攻击,即攻击者截获请求后,不对请求进行任何调整。直接使用截获的内容重新高频率发送请求。
提供X-Ca-Timestamp
、X-Ca-Nonce
(nonce
: number used once
)两个可选Http Header
,客户端调用API
时一起使用这两个参数,可以达到防止重放攻击的目的。
原理
X-Ca-Timestamp
:发起请求的时间,可以取自机器的本地实现。当网关收到请求时,会校验这个参数的有效性,误差不超过指定时间(如:15
分钟)。X-Ca-Nonce
:这个是请求的唯一标识,一般使用UUID
来标识。网关收到这个参数后会校验这个参数的有效性,同样的值,指定时间(如:15
分钟)只能被使用一次。X-Ca-Timestamp
、X-Ca-Nonce
和一起辅助参数都加入签名计算,所以请求的任何修改,都会造成签名失败。
实现
Filter 方式(推荐)
@Slf4j
@RequiredArgsConstructor
public class ReplayAttacksFilter extends OncePerRequestFilter {
private static final String X_CA_Key = "X-Ca-Key";
private static final String X_CA_TIMESTAMP = "X-Ca-Timestamp";
private static final String X_CA_NONCE = "X-Ca-Nonce";
private static final String X_CA_SIGNATURE = "X-Ca-Signature";
private static final String X_CA_SIGNATURE_METHOD = "X-Ca-Signature-Method";
private static final String X_CA_SIGNATURE_HEADERS = "X-Ca-Signature-Headers";
private static final long EXPIRE_MILLIS = 60_000;
private final Cache cache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String key = request.getHeader(X_CA_Key);
String timestamp = request.getHeader(X_CA_TIMESTAMP);
String nonce = request.getHeader(X_CA_NONCE);
String signature = request.getHeader(X_CA_SIGNATURE);
String signatureMethod = request.getHeader(X_CA_SIGNATURE_METHOD);
String signatureHeaders = request.getHeader(X_CA_SIGNATURE_HEADERS);
if (ObjectUtils.isEmpty(timestamp) || ObjectUtils.isEmpty(nonce) || ObjectUtils.isEmpty(signature)) {
log.error("Replay Attacks[{}] Invalid Http Headers: [{}, {}, {}], params: {}", request.getRequestURI(), timestamp, nonce, signature, StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8).replaceAll(System.lineSeparator(),""));
throw new ReplayAttacksException();
}
long ts = Long.parseLong(timestamp);
long currentTs = System.currentTimeMillis();
long diff = currentTs - ts;
if (Math.abs(diff) > EXPIRE_MILLIS) {
log.error("Replay Attacks[{}] timestamp[{}] diff[{}] is out of scope[{}], params: {}", request.getRequestURI(), timestamp, diff, EXPIRE_MILLIS, StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8).replaceAll(System.lineSeparator(),""));
throw new ReplayAttacksException();
}
String text = String.join("", timestamp, nonce);
String calcSign = DigestUtils.md5DigestAsHex(text.getBytes(StandardCharsets.UTF_8));
if (!Objects.equals(signature, calcSign)) {
log.error("Replay Attacks[{}] Invalid Signature[{}], Raw text: {}, Calc Sign: {}, params: {}", request.getRequestURI(), signature, text, calcSign, StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8).replaceAll(System.lineSeparator(),""));
throw new ReplayAttacksException();
}
Object value = cache.get(text);
if (Objects.nonNull(value)) {
log.error("Replay Attacks[{}] Exists Nonce[{}], Timestamp is {}", request.getRequestURI(), nonce, value);
throw new ReplayAttacksException();
}
cache.put(Cache.Prefix.REPLAY_ATTACKS + signature, timestamp, EXPIRE_MILLIS, TimeUnit.MILLISECONDS);
filterChain.doFilter(request, response);
} catch (ReplayAttacksException | NumberFormatException e) {
response.setStatus(HttpStatus.FORBIDDEN.value());
}
}
}
注册Filter
实例。
@Configuration(proxyBeanMethods = false)
public class ReplayAttacksConfig {
@Bean
public FilterRegistrationBean<ReplayAttacksFilter> replayAttacksFilter(Cache cache) {
FilterRegistrationBean<ReplayAttacksFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new ReplayAttacksFilter(cache));
filterRegistrationBean.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1); // after TraceFilter
filterRegistrationBean.setUrlPatterns(Collections.singleton(Url.Api.FILTER_PATTERN));
return filterRegistrationBean;
}
}
Interceptor 方式
Interceptor
方式只是与Filter
方式的注册方式和作用于不一样,Interceptor
作用时间更晚于Filter
。
Interceptor
在Spring
提供的MVC
统一入口DispatcherServlet
的doDispatch
方法中调用applyPreHandle
方法被执行。
RequestBodyAdvice 方式
RequestBodyAdvice
作用时间更晚于Interceptor
。
参考
Views: 2,201 · Posted: 2023-01-23
————        END        ————
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓
Loading...