1、接口重放的定义
接口重放是一种常见的安全需求,特别是api接口在网络通信中,攻击者捕捉并重放发送有效的请求,进行探测,分析 从而获取可利用的信息,进一步进行攻击, 达到非法目的。如何防止重复提交请求、减少接口攻击风险,在实际的开发中需确保每个请求的唯一性和时效性。以下是通过研究常见的几种防止接口重放攻击策略
2、解决措施
通过引入时间戳 timestamp 、和唯一随机数 nonce one-time-number 可以确保每个请求在服务器端具有唯一性
2.1时间戳
通过引入时间戳 timestamp记录web端当前触发接口的开始时间,服务器端可以验证特定时间范围内的运行的请求
2.2唯一随机数
每个请求都包含一个唯一的,不可预测的值,服务器保存已经处理过的nonce 确保每个请求的唯一性
2.3签名验签
通过签名机制,服务器校验来自客户端的请求是否被篡改,从而判断其合法性
3、验证实验
3.1环境
本套测试使用 java+springboot+jsp的整合研究(应用程序搭建自行部署)
3.2思考
3.2.1生成时间戳timestamp和nonce
当客户端发送请求时,请求接口自带当前的时间戳和唯一的随机生成的nonce
3.2.2如何生成
后端生成通过 model视图返回前端表单、接口发送请求携带此参数即可
3.3具体实现
3.3.1前端jsp代码加上 timestamp和nonce
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录</title>
</head>
<body>
<h2>登录</h2>
<form action="/nonce" method="POST"><label for="username">用户名:</label><input type="text" id="username" name="username" required/><br/><label for="password">密码:</label><input type="password" id="password" name="password" required/><br/><!-- 加入时间戳和Nonce --><input type="hidden" name="timestamp" value="${timestamp}"/><input type="hidden" name="nonce" value="${nonce}"/><button type="submit">登录</button>
</form>
<p style="color:red">时间戳${timestamp}</p >
<p style="color:red">一次性校验码${nonce}</p >
<p style="color:red">${error}</p >
<p style="color:darkmagenta">${success}</p >
</body>
</html>
控制层用户接收来自客户端的请求
通过 model.addAttribute("timestamp", timestamp); 和 model.addAttribute("nonce", nonce); 将生成的时间戳和随机 nonce 传递到 JSP 页面,以便在客户端使用
@Controller
public class NonceContrller {private static final Set<String> usedNonces = new HashSet<>();@GetMapping("/nonce")public String showNonce(Model model) {// 生成一个时间戳和一个随机的 noncelong timestamp = System.currentTimeMillis();String nonce = UUID.randomUUID().toString();// 将时间戳和nonce传递到JSP页面model.addAttribute("timestamp", timestamp);model.addAttribute("nonce", nonce);System.out.println(nonce);return "nonce"; // 返回login.jsp视图}
3.3.2时间戳回显
启动spingboot 项目访问 http://192.168.3.118:8080/nonce 则出现一下内容 界面的时间戳和一次性校验码则返回前端
接口值携带时间戳和一次性校验码
后端代码校验
通过启动springboot服务器, 向服务器发送请求登录
提交form表单,信息内容提交至服务器端 服务器端获取来自前端的参数进行判断
其中
final long MAX_TIMESTAMP_DIFF1 = 15 * 1000; // 15秒 时间戳15s后过期
usedNonces.contains(nonce)判断来自客户端的nonce值是否被使用
二者均满足则服务器合法处理
@PostMapping("/nonce")public String handleLogin(@RequestParam String username, @RequestParam String password,@RequestParam long timestamp, @RequestParam String nonce,Model model) {final long MAX_TIMESTAMP_DIFF1 = 15 * 1000; // 15秒long currentTimestamp = System.currentTimeMillis();long times = currentTimestamp - timestamp;System.out.println("时间差: " + times);// 1. 时间超时校验if (times > MAX_TIMESTAMP_DIFF1) {model.addAttribute("error", "时间超时,请重新登录");return "nonce"; // 返回登录页面}// 2. 验证 nonce 是否已使用过if (usedNonces.contains(nonce)) {model.addAttribute("error", "请求重复,请重新登录");return "nonce"; // 返回登录页面}// 3. 进行用户身份验证String sql = "SELECT * FROM users WHERE username = ? AND password = ?";try (Connection connection = getConnection();PreparedStatement preparedStatement = connection.prepareStatement(sql)) {preparedStatement.setString(1, username);preparedStatement.setString(2, password); // 注意:应使用加密后的密码ResultSet resultSet = preparedStatement.executeQuery();if (resultSet.next()) {// 4. 用户存在且密码匹配,nonce 记录到 usedNonces,防止重放usedNonces.add(nonce);model.addAttribute("success", "登录成功");return "home"; // 跳转到首页} else {// 5. 登录失败但 nonce 不能存入model.addAttribute("error", "用户名或密码错误,请重新登录");// model.addAttribute("refresh", true);return "redirect:/nonce";}} catch (SQLException e) {e.printStackTrace();model.addAttribute("error", "系统错误,请稍后重试");return "nonce";}}
超期校验,刷新接口 待生成时间戳 15s后向服务器发送请求则提示超时
重放校验,拦截请求重放发送 nonce使用则提示请求重复