目录
简介
在构建安全可靠的Web应用程序时,防止请求重放攻击是一项关键任务。当用户或系统发出的请求被恶意第三方捕获并重复发送给服务器时,就会发生重放攻击。这可能导致不必要的操作被执行,如重复下单、重复支付等。为了保护我们的应用免受此类威胁,我们可以利用Spring MVC框架提供的强大工具——HandlerInterceptor来实现防重放机制。
HandlerInterceptorAdapter vs HandlerInterceptor
当你决定为自己的应用添加自定义拦截逻辑时,可能会面临选择:是继承 HandlerInterceptorAdapter
还是直接实现 HandlerInterceptor
接口?两者之间的差异在于设计哲学和代码的简洁性。
HandlerInterceptorAdapter
是一个适配器类,它实现了HandlerInterceptor
接口,并提供了所有方法的空实现。因此,如果你只需要覆盖部分方法,使用这个适配器可以让你的代码更加简洁。HandlerInterceptor
接口则要求你必须显式地实现接口中定义的所有方法。这种方式虽然可能引入一些冗余代码,但它提供了更高的透明度,因为你清楚地知道有哪些方法需要处理。- 总结:extends HandlerInterceptorAdapter 只重写需要的方法即可,implements HandlerInterceptor需要重写所有方法,可能增加代码冗余
创建一个防重放拦截器
为了确保相同的请求不会被多次处理,我们需要在每次接收到POST请求时检查其唯一标识符(例如订单号或时间戳),并与服务器端存储的历史记录进行比较。下面是一个简单的防重放拦截器实现示例,这里我们使用Redis作为存储介质:
@Component
public class AntiReplayInterceptor extends HandlerInterceptorAdapter {
private static final String REQUEST_ID = "requestId";
private static final long EXPIRE_TIME_IN_SECONDS = 60 * 5;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if ("POST".equals(request.getMethod()) &&
request.getRequestURI().startsWith("/inner/public/syncOrder/v1")) {
// 获取请求体中的唯一标识符
SynYxcRequest orderSyncDto = extractRequestBody(request);
String requestId = orderSyncDto.getRequestId();
// 检查是否已经处理过该请求ID
if (redisTemplate.opsForValue().get(requestId) != null) {
response.setStatus(HttpServletResponse.SC_CONFLICT);
return false;
} else {
// 将请求ID存入Redis,并设置过期时间
redisTemplate.opsForValue().set(requestId, "processed", EXPIRE_TIME_IN_SECONDS, TimeUnit.SECONDS);
}
}
return true;
}
private SynYxcRequest extractRequestBody(HttpServletRequest request) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(request.getReader().lines().collect(Collectors.joining(System.lineSeparator())), SynYxcRequest.class);
}
}
注册拦截器
有了拦截器之后,我们需要将其注册到Spring MVC配置中,以确保它能够应用于指定的请求路径:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AntiReplayInterceptor antiReplayInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(antiReplayInterceptor)
.addPathPatterns("/inner/public/syncOrder/v1");
}
}
路径模式匹配
如果不指定 .addPathPatterns()
方法,那么拦截器将会默认应用到所有路径。为了避免不必要的性能开销以及潜在的问题,你应该总是明确地定义你要拦截的路径模式。此外,你还可以使用 .excludePathPatterns()
来排除不需要拦截的路径。
适配器模式的魅力
通过使用 HandlerInterceptorAdapter
,你可以轻松地只覆盖那些对你重要的方法,而无需关心其他方法。这种设计不仅简化了代码,也提高了可维护性。当然,如果你更喜欢一切都在掌控之中,直接实现 HandlerInterceptor
接口也是一个不错的选择。
总结
通过本文介绍的内容,我们了解了如何利用Spring MVC中的 HandlerInterceptor
和 HandlerInterceptorAdapter
来构建一个高效的防重放拦截器。无论你是追求代码简洁还是完全控制,Spring都提供了灵活的解决方案,帮助你在Web开发的世界里游刃有余。希望这些知识能为你的项目增添一份安全保障,让每一次请求都能得到恰当的处理。