LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

如何防止恶意伪造前端唯一请求id

zhenglin
2025年12月4日 9:43 本文热度 88


单纯依赖前端携带的唯一请求ID(如Idempotent-Request-Id),确实存在被恶意伪造的风险(比如攻击者伪造他人的有效ID、重复使用已失效的ID、生成非法格式的ID),可能导致:他人请求被误拦截、恶意重复操作绕过幂等校验、系统资源被滥用等问题。

在企业级开发中,防止ID伪造的核心思路是:给“唯一请求ID”加上“身份绑定+合法性校验+时效性”,让ID只能被合法用户在合法时间内用于合法业务,而非孤立地校验ID本身。

下面是企业级常用的、从易到难的4层防护方案,可根据业务敏感度(如普通表单提交vs支付转账)组合使用:



一、基础防护:ID与用户身份强绑定(必选,零成本)

这是最核心、最常用的一层防护,也是之前后端逻辑的延伸——让唯一请求ID和“用户身份”深度绑定,即使ID被伪造,没有对应的用户身份也无法生效。

实现原理

后端校验幂等时,不仅要检查Idempotent-Request-Id是否存在,还要确保该ID属于当前请求的合法用户,避免“伪造他人ID”导致的误拦截或恶意操作。

企业级实现方案(已融入之前的后端逻辑,强化说明)

  • 前端必须携带用户身份标识:除了Idempotent-Request-Id,还需携带用户登录后的合法凭证(如Token/JWT),后端从凭证中解析出真实的userId(而非前端单独传递User-Id请求头,避免篡改);

  • 后端Redis键的构成idempotent:${userId}:${requestURI}:${requestId}(用户ID+接口路径+请求ID),三者缺一不可;

  • 核心逻辑:即使攻击者伪造了一个有效的requestId,但如果无法提供对应的userId的合法Token,后端解析出的userId不匹配,Redis键也不会命中,无法触发重复拦截;同时,不同用户的ID完全隔离,不会相互影响。


代码强化(后端拦截器修改,防止User-Id被篡改)

之前的代码中User-Id是前端传递的,存在被篡改风险,企业级更推荐从Token解析:

// 幂等拦截器(IdempotentInterceptor)修改preHandle方法

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    // ... 省略其他逻辑 ...


    // 关键修改:从Token解析userId,而非前端传递的User-Id请求头

    String token = request.getHeader("Authorization");

    if (token == null || token.trim().isEmpty()) {

        throw new IdempotentException("未登录,请先登录!");

    }

    // 解析Token(企业级通常用JWT工具类,这里简化)

    Long userId = parseUserIdFromToken(token); // 核心:从合法凭证中获取真实用户ID

    if (userId == null) {

        throw new IdempotentException("Token无效,无法验证身份!");

    }


    // 构建Redis键(用户ID+接口路径+请求ID),防止伪造

    String redisKey = String.format("idempotent:%s:%s:%s", userId, request.getRequestURI(), requestId);


    // ... 后续Redis校验逻辑不变 ...

}


// 从JWT Token解析用户ID(示例方法)

private Long parseUserIdFromToken(String token) {

    try {

        // 企业级常用JJWT工具类解析

        Claims claims = Jwts.parser()

                .setSigningKey("your-secret-key") // 企业级需用非对称加密(如RSA)

                .parseClaimsJws(token.replace("Bearer ", ""))

                .getBody();

        return claims.get("userId", Long.class);

    } catch (Exception e) {

        return null; // Token无效

    }

}

防护效果

  • 攻击者无法伪造他人的userId(需破解Token),即使伪造了requestId,也因userId不匹配而无法生效;

  • 杜绝“伪造他人ID导致他人请求被拦截”的风险。


二、进阶防护:ID合法性校验(防止伪造非法ID)

基础防护解决了“身份绑定”问题,但攻击者可能生成大量非法格式的requestId(如超长字符串、特殊字符),试图耗尽Redis资源(恶意攻击)。这一层防护用于校验requestId本身的合法性。

实现方案

  • 前端规范ID格式:明确requestId的生成规则(如UUIDv4,32位无横线字符串,或“时间戳+8位随机数”),并在前端代码中强制约束;

  • 后端校验ID格式:在拦截器中添加requestId的格式校验,非法格式直接拒绝,不进入Redis存储流程;

  • 企业级推荐格式:UUIDv4(32位字母数字组合),格式固定,便于校验。


代码实现(后端拦截器添加格式校验)

// 幂等拦截器中,获取requestId后添加格式校验

String requestId = request.getHeader(IDEMPOTENT_REQUEST_ID);

if (requestId == null || requestId.trim().isEmpty()) {

    throw new IdempotentException("缺少幂等请求ID,请重试!");

}


// 校验requestId格式(UUIDv4,32位字母数字)

String regex = "^[0-9a-fA-F]{32}$";

if (!requestId.matches(regex)) {

    throw new IdempotentException("幂等请求ID格式非法,请规范请求!");

}

防护效果

  • 过滤非法格式的requestId,避免Redis存储垃圾数据,防止恶意攻击耗尽Redis资源;

  • 增加攻击者伪造的难度(需符合特定格式)。


三、敏感场景防护:ID签名校验(防止篡改/伪造,适用于支付、转账)

对于支付、转账、订单创建等核心敏感场景,仅靠身份绑定和格式校验还不够——攻击者可能窃取合法用户的TokenrequestId(如通过网络劫持),进行重放攻击。此时需要给requestId加上“签名”,确保ID是用户真实生成的,未被篡改。

实现原理

前端生成requestId后,用“用户唯一密钥+请求参数+时间戳”对requestId进行签名,后端验证签名合法性:只有签名通过,才认为requestId是合法的,否则直接拒绝。



企业级实现步骤

1. 前端实现(生成签名)

  • 核心逻辑:签名 = MD5(requestId + userId + 时间戳 + 密钥)(密钥需安全存储,如前端环境变量,避免明文写死);

  • 需传递的请求头:

    • Idempotent-Request-Id:生成的唯一ID;

    • Idempotent-Timestamp:生成ID时的时间戳(精确到秒);

    • Idempotent-Sign:上述签名;

    • Authorization:用户Token(用于解析userId)。


// 前端生成签名的工具函数(Vue示例)

const generateIdempotentSign = (requestId, userId, timestamp, secretKey) => {

  // 拼接签名原文(requestId+userId+时间戳+密钥)

  const signStr = `${requestId}${userId}${timestamp}${secretKey}`;

  // MD5加密(企业级可用SHA256,安全性更高)

  return CryptoJS.MD5(signStr).toString();

};


// 下单请求时携带签名

const handleCreateOrder = async () => {

  const requestId = crypto.randomUUID().replace(/-/g, ''); // 32位UUID

  const timestamp = Math.floor(Date.now() / 1000); // 时间戳(秒)

  const userId = localStorage.getItem('userId'); // 从本地存储获取(需确保已登录)

  const secretKey = import.meta.env.VITE_IDEMPOTENT_SECRET; // 环境变量存储密钥(非明文)


  // 生成签名

  const sign = generateIdempotentSign(requestId, userId, timestamp, secretKey);


  // 发送请求

  const res = await axios.post(

    '/order/create',

    { productId: 1001, amount: 99.9 },

    {

      headers: {

        'Authorization': `Bearer ${localStorage.getItem('token')}`,

        'Idempotent-Request-Id': requestId,

        'Idempotent-Timestamp': timestamp,

        'Idempotent-Sign': sign

      },

      idempotent: true

    }

  );

};

2. 后端实现(验证签名)

  • 校验时间戳有效性(防止签名被长期复用);

  • 用相同规则重新计算签名,与前端传递的签名比对;

  • 密钥需前后端一致,企业级推荐后端动态下发临时密钥(而非固定密钥)。

代码高亮:

// 幂等拦截器中添加签名校验逻辑

private void validateSign(HttpServletRequest request, String requestId, Long userId) {

    // 1. 获取前端传递的时间戳和签名

    String timestampStr = request.getHeader("Idempotent-Timestamp");

    String sign = request.getHeader("Idempotent-Sign");

    if (timestampStr == null || sign == null) {

        throw new IdempotentException("缺少幂等签名信息,请规范请求!");

    }


    // 2. 校验时间戳(有效期5分钟,防止重放攻击)

    long timestamp = Long.parseLong(timestampStr);

    long currentTime = System.currentTimeMillis() / 1000;

    if (Math.abs(currentTime - timestamp) > 300) { // 5分钟=300秒

        throw new IdempotentException("请求已过期,请重新发起!");

    }


    // 3. 后端重新计算签名(与前端规则一致)

    String secretKey = "your-frontend-secret"; // 企业级推荐动态下发,而非固定

    String signStr = String.format("%s%s%s%s", requestId, userId, timestampStr, secretKey);

    String serverSign = DigestUtils.md5DigestAsHex(signStr.getBytes(StandardCharsets.UTF_8));


    // 4. 比对签名(不相等则为伪造)

    if (!serverSign.equalsIgnoreCase(sign)) {

        throw new IdempotentException("幂等签名非法,请求被拒绝!");

    }

}


// 在preHandle方法中调用签名校验

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    // ... 省略前面的逻辑(获取requestId、解析userId) ...


    // 敏感场景添加签名校验

    validateSign(request, requestId, userId);


    // ... 后续Redis校验逻辑 ...

}

防护效果

  • 即使攻击者窃取了requestIdToken,也无法伪造签名(缺少密钥和时间戳);

  • 时间戳有效期限制,防止签名被长期复用(重放攻击);

  • 适用于支付、转账等核心敏感场景,是企业级高安全要求的标准配置。

四、终极防护:结合业务上下文校验(防止ID被滥用)

对于极端敏感的场景(如大额转账、核心订单操作),还需要将requestId与“业务参数”绑定,确保ID仅用于特定业务,防止攻击者用A业务的有效ID去请求B业务。

实现原理

后端Redis键中加入“业务唯一标识”(如商品ID、订单号、转账金额),确保requestId只能用于该业务,无法跨业务复用。


代码示例(Redis键添加业务参数)

// 下单接口的幂等Redis键(添加商品ID)

String productId = request.getParameter("productId"); // 从请求参数中获取业务参数

String redisKey = String.format("idempotent:%s:%s:%s:%s", userId, request.getRequestURI(), requestId, productId);

防护效果
  • 攻击者即使获取了下单接口的requestId,也无法用它去请求支付接口或其他商品的下单接口;

  • 进一步缩小requestId的适用范围,防止跨业务滥用。


五、企业级方案选型建议(按业务敏感度匹配)


 

关键注意点(企业级避坑)

密钥安全:前端签名用的密钥,不能明文写在代码中,需用环境变量(如VITE_XXX)或后端动态下发(如登录后返回临时密钥,有效期1小时);

非对称加密:敏感场景的签名,推荐用RSA非对称加密(前端用公钥加密,后端用私钥解密),避免密钥泄露;

Token安全:必须用HTTPS传输TokenrequestId,防止网络劫持窃取;

不依赖前端单独传递用户身份:始终从Token/Session解析userId,拒绝前端传递的User-Id(易被篡改)。


总结

核心结论:防止唯一请求ID伪造,企业级的核心是“身份绑定+合法性校验”,敏感场景叠加“签名校验+业务上下文校验”,而非孤立校验ID本身;

方案特点:从基础到进阶,成本逐步提升,安全等级也逐步提高,可根据业务敏感度灵活组合;

落地建议:先实现“身份绑定(Token解析userId)+ ID格式校验”(零成本、高收益),核心场景再叠加签名校验,无需过度设计;

本质逻辑:让“唯一请求ID”成为“用户身份+业务场景+时间窗口”的复合标识,伪造者需同时破解多个维度才能生效,难度指数级提升。


按这套方案实现后,就能有效抵御绝大多数恶意伪造唯一请求ID的攻击,完全满足企业级应用的安全要求。



参考文章:原文链接


该文章在 2025/12/4 9:43:59 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved