如何防止接口被恶意请求?添加时间戳检验?

添加时间戳校验位

比如给客户端提供一个timestamp参数,值是13位的毫秒级时间戳,可以在第12位或者13位做一个校验位,通过一定的算法给其他12位的值做一个校验。

举例:现在实际时间是 1684059940123,我把前12位通过算法算出来校验位的值是9,参数则把最后一位改成9,即1684059940129,值传到服务端后通过前十二位也可以算出来值,来判断这个时间戳是不是合法的。

生成校验位的算法要确保前后端一致,比如校验位的值都取倒数第二位的值(可过滤掉90%)恶意请求。

添加拦截器防止接口恶意狂刷

其实也就是spring拦截器来实现。在需要防刷的方法上,加上防刷的注解,拦截器拦截这些注解的方法后,进行接口存储到redis中。当用户多次请求时,我们可以累积他的请求次数,达到了上限,我们就可以给他提示错误信息。

添加注解

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface AccessLimit {

    int seconds();

    int maxCount();

}

自定义拦截器

public class SessionInterceptor extends HandlerInterceptorAdapter {

    private static final Logger logger = LoggerFactory.getLogger(SessionInterceptor.class);

    @Autowired

    private UserAuthService userAuthService;

    @Autowired

    private UserContext userContext;

 @Autowired

 private RedisUtil redisUtil;

    @Override

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

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

        if (StringUtils.isEmpty(session)) {

            throw new UserException(ReturnCode.PARAMETERS_ERROR, "缺少session");

        }

        HandlerMethod hm = (HandlerMethod) handler;

        //获取方法中的注解,看是否有该注解

        AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);

        if(accessLimit != null){

         int seconds = accessLimit.seconds();

         int maxCount = accessLimit.maxCount();       

         //从redis中获取用户访问的次数

         String ip = request.getHeader("x-forwarded-for"); // 有可能ip是代理的

            if(ip ==null || ip.length() ==0 || "unknown".equalsIgnoreCase(ip)) {      

                ip = request.getHeader("Proxy-Client-IP");      

            }      

            if(ip ==null || ip.length() ==0 || "unknown".equalsIgnoreCase(ip)) {      

                ip = request.getHeader("WL-Proxy-Client-IP");      

            }      

            if(ip ==null || ip.length() ==0 || "unknown".equalsIgnoreCase(ip)) {      

                 ip = request.getRemoteAddr();      

            } 

         Integer count = (Integer)redisUtil.get(RedisKey.SECOND_ACCESS + ip);

         if(count == null){

          //第一次访问

        redisUtil.set(RedisKey.SECOND_ACCESS + ip, 1, seconds);

         }else if(count < maxCount){

          //加1

          count = count + 1;

       redisUtil.incr(RedisKey.SECOND_ACCESS + ip, count);

         }else{

          //超出访问次数

                logger.info("访问过快ip ===> " + ip + " 且在 " + seconds + " 秒内超过最大限制 ===> " + maxCount + " 次数达到 ====> " + count);

          response.setCharacterEncoding("UTF-8");          response.setContentType("application/json; charset=utf-8");

          ReturnObject result = new ReturnObject();         result.setCode(ReturnCode.OPERATION_FAILED.getCode());

          result.setData("操作太快了");

          Object obj = JSONObject.toJSON(result);         response.getWriter().write(JSONObject.toJSONString(obj));

          return false;

         }

        }       

        Integer userId = userAuthService.getUserIdBySession(session);

        //获取登录的session进行判断

        if (userId == null || userId == 0) {

            throw new UserException(ReturnCode.BAD_REQUEST, "无效session");

        }       

        userContext.setUserId(userId);

        return super.preHandle(request, response, handler);

    }

}

 

添加拦截器

@Configuration

public class WebConfig extends WebMvcConfigurationSupport { 

    @Autowired

    private SessionInterceptor interceptor;

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(interceptor);

    }

}

这里采用了注解方式(拦截器),结合redis来存储请求次数,达到上限就不让用户操作。当然,redis有时间限制,到了时间用户可以再次请求接口的。

 

 

 

<think>嗯,用户问的是如何使用Redis防止重复下单。这个问题看起来挺常见的,尤其是在电商或者高并发系统中。我得先理清楚用户的需求。重复下单可能是指同一个用户在短时间内多次提交相同的订单,这会导致库存问题或者重复支付等情况。所以,用户需要一种机制来防止这种情况发生。 首先,我需要回忆一下Redis的特性。Redis是内存数据库,读写速度快,支持原子操作,适合处理高并发场景。常见的防重方法可能有几种,比如使用唯一标识、分布式锁、Token机制或者幂等性处理。但用户这里可能更关注的是如何具体用Redis实现。 接下来,我得想一下具体步骤。假设用户在下单时,可能因为网络延迟或者用户多次点击导致同一请求被多次发送到服务器。这时候,服务器需要判断这个请求是否已经处理过。 可能的解决方案之一是生成一个唯一的请求标识,比如用户ID加商品ID加时间戳之类的组合,然后将这个标识存入Redis,并设置一定的过期时间。每次请求到来时,先检查Redis中是否存在这个标识,如果存在就拒绝处理,否则继续处理。但这种方法需要注意并发情况下的原子性问题,比如多个请求同时检查是否存在这个标识,可能会同时通过检查,导致重复下单。 这时候可能需要用到Redis的setnx命令(SET if Not eXists),或者使用带有过期时间的SET命令,并确保原子性。比如使用SET key value NX EX 过期时间,这样可以在设置键的同时设置过期时间,避免重复下单。这里的key可以是订单的唯一标识,比如用户ID+商品ID或者其他业务相关的唯一组合。 另外,还可以考虑使用分布式锁。比如,当用户开始下单时,先获取一个锁,锁住用户ID或者订单相关资源,处理完后再释放锁。这样可以防止同一个用户的并发请求Redis的RedLock算法或者简单的setnx都可以用来实现分布式锁,但需要注意锁的超时时间和正确释放,避免死锁或者锁被其他进程误删。 还有一种方法是利用Redis的原子递增操作。例如,为每个用户和商品生成一个key,每次下单前先检查这个key的值是否超过阈值,或者通过INCR命令来计数,如果计数超过1则拒绝。但这种方法可能不如设置唯一标识直接有效。 另外,幂等性处理也是一个思路。服务端在接收到请求时,先生成一个唯一的token返回给客户端,客户端下单时必须携带这个token。服务器在处理请求时,先检查token是否存在,存在则处理并删除token,否则拒绝。这种方法需要客户端配合,可能不适合所有场景。 不过,用户的问题更偏向于如何用Redis防止重复下单,所以可能最适合的是使用唯一key加过期时间,结合setnx命令。例如,当用户提交订单时,生成一个唯一的key,如order:user123:product456,然后尝试在Redis中设置这个key,如果成功(即之前不存在),则处理订单,并设置一个较短的过期时间(比如30秒)。如果设置失败,说明已经有相同的订单在处理中,直接返回重复提交的错误。 需要注意的是,这个key的生成规则必须确保唯一性,比如结合用户ID、商品ID、时间戳(比如分钟级,避免同一用户同一商品在一分钟内重复下单),或者使用更精确的业务字段组合。同时,过期时间的设置要根据业务需求来定,太短可能无法有效防止重复,太长可能影响用户体验。 可能还要考虑不同客户端的重复提交,比如用户在手机和电脑同时提交,这时候可能需要更复杂的机制,但基本的防重复下单还是可以用上述方法。 总结来说,步骤大概是:生成唯一请求标识,用Redis的setnx或SET NX EX命令存入,处理订单前检查是否存在,存在则拒绝,否则继续处理,并设置合适的过期时间。同时要考虑并发情况下的原子性操作,确保即使多个请求同时到达,只有一个能成功设置key,从而防止重复下单。</think>使用 Redis 防止重复下单的核心思路是通过**唯一标识+原子性操作**拦截重复请求,以下是具体实现步骤和注意事项: --- ### **1. 核心方案:基于唯一请求标识** #### **步骤说明** 1. **生成唯一标识** 将用户ID、商品ID、时间戳(如秒级/分钟级)等业务参数拼接,或通过哈希(如MD5)生成唯一标识,例如: `order_lock:user123:product456` 或 `order_lock:MD5(用户ID+商品ID+下单时间戳)`。 2. **Redis 写入校验** 使用 `SET key value NX EX seconds` 命令(原子性操作): - **NX**:仅当 Key 不存在时才能写入,避免并发覆盖。 - **EX**:设置自动过期时间(根据业务设定,如 10~30 秒)。 ```bash SET order_lock:user123:product456 1 NX EX 30 ``` 3. **校验结果处理** - 如果写入成功 → 允许下单,继续后续业务流程。 - 如果写入失败 → 直接返回 "请勿重复提交" 提示。 4. **过期时间结束后** Redis 自动删除 Key,允许用户再次正常下单。 --- ### **2. 进阶优化:防止极端并发场景** #### **场景问题** 若用户疯狂点击导致同一毫秒内大量并发请求,可能绕过时间戳拼接的唯一性。可补充以下方案: - **方案一:分布式锁(更严格)** 下单前对用户ID加锁(如 `lock:user123`),通过 Redis 的 `SETNX` 或 `Redisson` 库实现锁竞争,确保同一用户串行处理请求。 - **方案二:Token 机制(需前端配合)** 1. 前端下单前先请求服务端获取一次性 Token(存 Redis 并设置过期时间)。 2. 提交订单时携带 Token,服务端验证 Token 存在后删除,防止重复使用。 --- ### **3. 关键注意事项** 1. **原子性操作** 必须使用 `SET NX EX` 或 Lua 脚本确保“判断+写入”的原子性,避免高并发下多个请求同时通过校验。 2. **合理设置过期时间** 根据业务容忍度设置,例如: - 短时效(10~30秒):拦截短时间内的重复点击。 - 长时效(分钟级):防止恶意用户周期性重试。 3. **异常补偿机制** 若下单失败(如支付超时),需手动删除 Redis Key 或设置更短的过期时间,避免阻塞用户正常重试。 --- ### **4. 代码示例(Python)** ```python import redis import hashlib def generate_order_key(user_id, product_id): # 示例:生成唯一Key(可结合时间戳) return f"order_lock:{user_id}:{product_id}" def prevent_duplicate_order(user_id, product_id): r = redis.Redis(host='localhost', port=6379) key = generate_order_key(user_id, product_id) # 原子性写入,NX=True(不存在才写入),EX=30秒 success = r.set(key, "1", ex=30, nx=True) if not success: return {"code": 400, "msg": "检测到重复下单,请稍后再试"} else: # 正常处理订单逻辑... return {"code": 200, "msg": "下单成功"} ``` --- ### **总结** 通过 Redis 的原子性操作和唯一Key机制,能高效拦截 99% 的重复下单场景。结合分布式锁或 Token 机制可进一步优化极端情况,最终根据业务需求平衡安全性与用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值