springboot框架项目实践(拦截器+redis实现接口防刷)

SpringBoot】28 API接口Redis + 拦截器 常用的 API 安全措施包括:火墙、验证码、鉴权、IP限制、数据加密、限流、监控、网关等,以确保接口的安全性。 阅读详情

1.引言

接口防刷这个事情,其实就是限流。在我们探讨服务容错的方案中,通常有以下可选择的方案

  • 超时:我调用你,你迟迟不给响应,超过一定时间我就不等你了,免得受到你的影响
  • 流控(限流):你们怎么都一起来找我,我一次只能服务3个人,超过3个的我管不了
  • 熔断降级:资源不够用不可用,为了保障核心链路可用,其它的都让一让

那么针对以上服务容错方案,业界可选择的产品比较丰富的,比如说:

  • 服务之间调用,不管是ribbon+restTemplate,还是feign都支持超时设置
  • 流控、熔断降级可选择的开源组件有: hystrix、resilience4j、sentinel

这些方案组件从功能、特性都比较丰富强大,要用好,需要团队中有专门的小伙伴去研究吃透,简单来说就是使用成本相对比较高,适合在平台级产品线中去使用。

那么,如果我们是一个创业型的小团队,产品线还没有那么丰富,一些临时性活动支撑。

举个例子,中秋节了,领导决定做一个客户、用户、员工线上答谢活动,即临时组织一个线上秒杀活动,那么这个任务,自然就交给了技术组的小伙伴。

如何实现接口防刷呢?前面我们提到方案肯定是来不及!有没有合适的方案?答案是有:spring拦截器+redis方案。下面来看具体实现。

2.环境准备

2.1.pom.xml

springboot整合redis使用,需要导入spring-boot-starter-data-redis依赖

 <dependencies>
     <!--web mvc依赖-->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>

     <!--redis 依赖-->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>

     <!--fast json依赖-->
     <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.43</version>
     </dependency>

     <!--lombok依赖-->
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
     </dependency>

     <!--test 依赖-->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
     </dependency>
</dependencies>

2.2.application.yml

server:
  port: 8080
spring:
  application:
    name: follow-me-springboot-interceptor
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    jedis:
      pool:
        min-idle: 8
        max-idle: 8
        max-active: 100
        max-wait: 100000ms
    timeout: 5000ms

2.3.redis相关

为了方便使用,编写一个redis配置类,以及一个redis工具类

  • RedisConfig:用于初始配置RedisTemplate模板工具,指定key/value相关的序列化实现
  • RedisUtil:redis工具类,在RedisTemplate模板的基础上,封装redis相关操作,更加方便业务使用

RedisConfig

/**
 * Redis配置类
 *
 * @author ThinkPad
 * @version 1.0
 */
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {

    /**
     * redisTemplate
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<Object, Object> template = new RedisTemplate<>();

        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        // value值的序列化采用fastJsonRedisSerializer
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);

        // 设置连接工厂
        template.setConnectionFactory(redisConnectionFactory);

        return template;
    }

    /**
     * stringRedisTemplate
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {

        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

        return template;
    }

}

RedisUtil

整个工具类,代码量比较大,需要完整看的小伙伴请到代码仓库看,我已经把整个案例代码上传到代码仓库

/**
 * Redis工具类
 *
 * @author ThinkPad
 * @version 1.0
 */
@Component
public final class RedisUtil {

    /**
     * 注入redisTemplate
     */
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    
    .........................省略其它代码....................
     /**
     * 获取缓存
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 存入缓存
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long inCr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }
    
    .........................省略其它代码....................
}

2.4.拦截器相关

编写一个拦截器,以及拦截器的配置

  • WebConfig:该配置类实现WebMvcConfigurer,用于web mvc相关的配置,本案例中用于配置拦截器
  • AccessLimit:注解,用于标注需要访问限制的接口
  • LimitInterceptor:访问限制处理拦截器

WebConfig

/**
 * web 配置
 *
 * @author ThinkPad
 * @version 1.0
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LimitInterceptor limitInterceptor;

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加限流拦截器
        registry.addInterceptor(limitInterceptor);

    }
}

AccessLimit

/**
 * 自定义注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {

 // 过期时间 单位:秒
 int seconds();

 // 最大请求次数
 int maxCount();

}

LimitInterceptor

/**
 * 限流 interceptor
 *
 * @author ThinkPad
 * @version 1.0
 */
@Component
@Slf4j
public class LimitInterceptor extends HandlerInterceptorAdapter {

 @Autowired
 private RedisUtil redisUtil;

 /**
  * 前置处理
  * @param request
  * @param response
  * @param handler
  * @return
  * @throws Exception
  */
  @Override
  public boolean preHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler) throws Exception {
      // 当前请求url
      String url = request.getRequestURI();

      // 判断请求是否属于方法的请求
      if(handler instanceof HandlerMethod){
          HandlerMethod hm = (HandlerMethod) handler;

          // 检查注解AccessLimit,若没有注解,直接放行
          AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
          if(accessLimit == null){
              log.info("当前正在请求接口:{},该接口不需要限制访问.", url);
              return true;
          }

          // 过期时间,最大请求次数
          int seconds = accessLimit.seconds();
          int maxCount = accessLimit.maxCount();
          log.info("当前正在请求接口:{},该接口有访问限制需求,时间:{},最大访问次数:{}",
                  url, seconds, maxCount);

          // 限流key
          String key = "rate:limit" + url;
          key = key.replaceAll("/", ":");
          Integer count = 0;

          // 从redis中获取用户访问的次数
          Object keyValue = redisUtil.get(key);
          if(keyValue != null){
              count = (Integer) keyValue;
          }

          // 第1次访问
          if(count == 0){
              redisUtil.set(key, 1, seconds);
          }else if(count < maxCount){
              // 第2到maxCount-1次访问
              redisUtil.inCr(key,1);
          }else{
              // 大于等于maxCount次访问
              String content = String.format("您正在访问的接口:%s,超出了访问限制阈值:%d",url, maxCount);
              render(response, content);
              return false;

          }


      }

    return true;

  }

  /**
   * 封装返回值
   * @param response
   * @param msg
   * @throws Exception
   */
  private void render(HttpServletResponse response, String msg)throws Exception {
      response.setContentType("application/json;charset=UTF-8");
      OutputStream out = response.getOutputStream();
      out.write(msg.getBytes("UTF-8"));
      out.flush();

      out.close();

  }

}

2.5.应用controller

/**
 * 限流controller
 *
 * @author ThinkPad
 * @version 1.0
 */
@RestController
public class RateLimitController {

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 测试方法
     * @return
     */
    @RequestMapping("noLimit")
    public String noLimit(){
        // 测试redis工具
        redisUtil.inCr("rate:limit:test", 1L);
        return "no limit.";
    }

    /**
     * 需要限流方法
     * @return
     */
    @RequestMapping("needLimit")
    @AccessLimit(seconds=60, maxCount=5)
    public String rateLimit(){

        return "need limit.";
    }
}

3.案例效果

启动应用,分别访问端点

  • 没有访问限制接口:http://127.0.0.1:8080/noLimit,响应:no limit.
  • 有访问限制接口:http://127.0.0.1:8080/needLimit
    • 未达到限制阈值,显示:need limit.
    • 达到限制阈值,限制:您正在访问的接口:/needLimit,超出了访问限制阈值:5

客户端连接redis,观察计数器

本文源码:gitee.com/yanghouhua/…,子模块: follow-me-springboot-interceptor

太强了!SpringBoot 3 接口的 8 种高效解决方案,你用对了吗 方案优点缺点适用场景Nginx限流性能高,护前置粒度粗,用户维度不可控静态资源、统一入口Redis+注解限流灵活,适配复杂场景侵入代码,复杂度高登录/操作类接口Guava限流轻量无依赖单机不可集群共享单体系统限流Sentinel限流支持控制台、维度丰富需额外部署 Sentinel 控制台微服务系统图形验证码止脚本攻击用户体验一般注册/登录滑块验证码智能行为识别接入复杂,成本高高安全场景IP白名单简洁、安全无法动态扩展内部系统/回调接口 阅读详情

相关推荐

springboot2.1+redis+拦截器 止表单重复提交

springboot2.1+redis+拦截器 止表单重复提交详细完整介绍,所用到的文件都上传了,下载即可使用。自己花了半天整理,并且测试通过,使用在实际项目中的,希望对每一个下载的朋友有帮助。

错误记录 springboot项目中在拦截器里使用redis获取值,出现空指针错误的原因及解决办法

出现情况: 在写博客项目时,要判断当前用户是否登录或是否已过期,所以实现了一个拦截器,注入redisUtil然后获取redis的用户登录信息,但明明redis中是存在数据的,还是一直获取为空。 出现原因: 拦截器SpringContext初始化之前就执行了,即Bean初始化之前它就执行了,所以肯定是无法获取SpringIOC容器中的内容的。 解决办法 我们就让拦截器执行的时候实例化拦截器Bean:在配置类里面先实例化拦截器,然后在添加拦截器时获取已实例化的拦截器Bean即可。 /** * @author

fengzhubuliuxue的博客 1304

STM32F103上跑mbedtls 2.24.0:手把手教你为MQTTS加密通信打好基础

本文详细介绍了如何在STM32F103上移植和裁剪mbedtls 2.24.0,为MQTTS加密通信打下坚实基础。通过版本选择、开发环境配置、深度裁剪策略和加密功能验证,帮助开发者在资源受限的MCU上实现安全通信。文章还提供了常见问题解决方案和性能优化技巧,助力物联网设备开发。

weixin_30321709的博客 438

拦截器中使用redisTemplate获取数据报空指针

​ 今天做了个小项目,使用自定义一个拦截器后使用redisTemplate获取redis缓存数据一直报null空指针。 ​ 随后使用了debug模式查找问题,发现token值获取到了,且redis中存在该缓存,但获取的还是null。 ​ 查看了代码发现没有什么问题: //如果未登陆 直接访问某个地址时 public class AuthcFilter extends FormAuthenticationFilter { @Autowired private RedisTempl

qq_48445961的博客 2796

SpringBoot】使用Redis

拦截器中使用RedisTemplate记录访问的次数。@Component/*Spring Data Redis 提供的一个模板类,用于简化与 Redis 数据库的交互类似于map类型,可以存储和检索各种类型的数据,包括字符串、对象、集合等。但是,它提供了一些额外的功能,如键的过期时间、哈希表操作、列表操作等。*/@Autowired/*** 在请求处理之后,视图渲染之前执行,用于进行资源清理*/@Override//输出接收的请求。

BlackPudding_的博客 1212

java拦截器怎么注入redis缓存

redis相关学习资料:https://edu.51cto.com/video/4196.htmlJava拦截器注入Redis缓存 在Java开发中,拦截器是一种常用的设计模式,它允许我们在代码执行前后插入一些额外的处理逻辑。而Redis是一个高性能的key-value存储系统,常用于缓存、消息队列等场景。将拦截器与Re...

weixin_41448653的博客 178

Springboot拦截器+redis实现暴力请求拦截

Springboot拦截器+redis实现暴力请求拦截

qq_46788415的博客 869

SpringBootSpring Boot 如何实现接口

SpringBoot接口

低调做人,低调编码,神码都是浮云 3097

Java--业务场景:SpringBoot 通过Redis进行IP封禁实现接口

在实际项目中,有些攻击者会使用自动化工具来频繁接口,造成系统的瞬时吞吐量提高,给系统带来很大的压力。要保障服务的安全性,需要止重要的接口被恶意新,接口的方式可以通过设置验证码,IP封禁,安全参数校验等方法。本文主要采用Redis将同一时间内频繁访问同一接口的IP封禁一段时间的方式来接口被恶意新。

芝麻馅的博客 2182

Springboot+Redis 实现API接口限流

前言 在开发分布式高并发系统时有三把利器用来保护系统:缓存、降级、限流。 缓存 缓存的目的是提升系统访问速度和增大系统处理容量 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开 限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理 本文主要讲的是api接口限流相关内容,虽然不是论述高并发概念中的限流, 不过道理都差不多。通过限流可以让系统维持在一个相对

oyc的博客 3967

后端分享】SpringBoot实现接口的5种实现方案!

创建一个Redis限流的Lua脚本,放在resources目录下的。

weixin_60937489的博客 1970

关于Springboot集成Redis实现接口

关于Springboot集成Redis实现接口 小贴士:目前大部分公司都采用前后端分离的开发方式,进行项目的并行开发。在项目中后台只需要提供一套API接口,就可以接入安卓、小程序、IOS、web等多个应用程序,这样可以节约开发成本。但是一个后台接入这么多应用程序的http请求,必然导致后端的压力非常大。所以对于一些请求进行过滤和拦截是非常有必要的,能够有效地减轻后台的压力。 接口机制:主要止短时间接口被大量调用(攻击),出现系统崩溃和系统爬虫问题,提升服务的可用性。 方案一(AOP) 一、什么是A

weixin_49919104的博客 967

一个注解搞定 SpringBoot 接口,真香!

大家好,我是猿人(猿码天地创始人),今天给大家聊聊接口的那些事。接口,顾名思义,想让某个接口某个人在某段时间内只能请求N次,或者某个接口对外在某一段时间内只允许访问N次。在项目中比...

猿码天地 4166

Springboot项目接口

说明:使用了注解的方式进行对接口的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作,  首先是写一个注解类: import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.El...

weixin_42533856的博客 7906

一个注解搞定 SpringBoot 接口,还有谁不会?

说明:使用了注解的方式进行对接口的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作。

IT界那些事儿 1126

Springboot项目接口(实例)

说明:使用了注解的方式进行对接口的功能,非常高大上,本文章仅供参考。 技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: importjava.lang.annotation.Retention; importjava.lang.annotation.Target; importstaticjava.lang.annotation.ElementType.METHOD; importstaticjava.lang.annotation.Retent...

心神沫沫的收藏 2700

SpringBoot API 接口

SpringBoot API 接口

mry6的博客 952

实现接口,最强方案在这!

优雅的实现接口,最强方案来了~!

1068
上一篇: 为什么 SpringBoot 宁可挨骂也要干掉 spring.factories?
下一篇: springboot框架项目实践应用(springcloud alibaba整合sentinel)
程序员职业指南
博客等级 码龄4年 2107粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值