springboot 接口防刷


springboot 接口防刷

                                 

                                           

*****************

接口防刷

             

刷接口造成的影响

循环访问接口,会使流量暴增,影响网站的正常对外服务
如果短信验证、支付接口遭到恶意访问,还会造成企业损失

                  

接口防刷措施

限制ip访问频率与并发数,可用nignx进行限制
limit_req_zone $binary_remote_addr zone=one:10m rate=20r/s;  #单个ip每秒请求数
limit_conn_zone $binary_remote_addr zone=addr:10m;           #单个ip每秒并发数

接口添加验证码,如短信接口发送短信前先使用验证码验证
重要接口在用户认证后才能访问,非认证用户不能访问
后端对接口限流,限制同一用户访问频率,可使用guava、Redisson提供的限流工具

                  

                         

*****************

示例:后端接口限流

                        

***********

配置文件

             

application.yml

spring:
  redis:
    host: 192.168.57.120
    port: 6379

                        

***********

dto 层

                  

ResponseResult

@Data
public class ResponseResult<T> {

    private String code;
    private String status;
    private String message;
    private T data;
}

                          

***********

annotation 层

             

RateLimiter

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {

    int userLimit() default 5;
    int ipLimit() default 10;
}

                        

***********

interceptor 层

                

CustomInterceptor

@Configuration
public class CustomInterceptor implements HandlerInterceptor {

    @Resource
    private RedissonClient client;

    private final ConcurrentHashMap<String,RRateLimiter> map = new ConcurrentHashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod){
            Method method = ((HandlerMethod) handler).getMethod();

            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            String userName = authentication.getName();

            String path=request.getServletPath();
            if (method.isAnnotationPresent(RateLimiter.class)){
                RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);

                RRateLimiter limiter;
                if (userName!=null && !userName.equals("anonymousUser")){
                    int userLimit = rateLimiter.userLimit();

                    String key=userName+path+"user-rate-limiter";
                    if (!map.containsKey(key)){
                        limiter = client.getRateLimiter(key);
                        limiter.setRate(RateType.PER_CLIENT,userLimit,1, RateIntervalUnit.MINUTES);

                        map.put(key,limiter);
                    }else {
                        limiter = map.get(key);
                    }

                }else {
                    int ipLimiter = rateLimiter.ipLimit();

                    String remoteAddr = request.getRemoteAddr();
                    String key=remoteAddr+path+"ip-rate-limiter";
                    if (!map.containsKey(key)){
                        limiter = client.getRateLimiter(key);
                        limiter.setRate(RateType.PER_CLIENT,ipLimiter,1, RateIntervalUnit.MINUTES);

                        map.put(key,limiter);
                    }else {
                        limiter = map.get(key);
                    }
                }

                if (limiter.tryAcquire()){
                    return true;
                }else {
                    throw new RuntimeException("请稍后重试");
                }
            }
        }

        return true;
    }
}

                    

***********

service 层

                  

CustomUserService

@Service
public class CustomUserService implements UserDetailsService {

    @Resource
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        return new User("gtlx",passwordEncoder.encode("123456"),
                Collections.singletonList(new SimpleGrantedAuthority("user")));
    }
}

                      

***********

config 层

                   

DataConfig

@Configuration
public class DataConfig {

    @Resource
    private RedisProperties redisProperties;

    @Bean
    public RedissonClient intiRedissonClient(){
        Config config=new Config();
        config.useSingleServer()
                .setAddress("redis://"+redisProperties.getHost()+":"+redisProperties.getPort());

        return Redisson.create(config);
    }
}

                

WebConfig

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private CustomInterceptor customInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(customInterceptor);
    }
}

                          

WebSecurityConfig

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    protected UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().and().authorizeRequests()
                .antMatchers("/hello").authenticated()
                .anyRequest().permitAll();
    }

    @Bean
    public PasswordEncoder initPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

                    

***********

controller 层

                 

HelloController

@RestController
public class HelloController {

    @RateLimiter(userLimit = 5)
    @RequestMapping("/hello")
    public ResponseResult<String> hello(){
        ResponseResult<String> result=new ResponseResult<>();
        result.setCode("000000");
        result.setStatus("success");
        result.setData("hello");

        return result;
    }

    @RateLimiter()
    @RequestMapping("/hello2")
    public ResponseResult<String> hello2(){
        ResponseResult<String> result=new ResponseResult<>();
        result.setCode("000000");
        result.setStatus("success");
        result.setData("hello2");

        return result;
    }

    @ExceptionHandler
    public ResponseResult<String> handleError(Exception e){
        ResponseResult<String> result=new ResponseResult<>();
        result.setCode("111111");
        result.setStatus("error");
        result.setMessage(e.getMessage());

        return  result;
    }
}

                       

                                    

*****************

使用测试

                   

localhost:8080/hello                             

                    

               

认证通过后,输出

                     

                    

 连续点击5次,输出

                     

                       

                        

localhost:8080/hello2(重启应用)

                     

                

连续点击10次(ip限流),输出

                     

                                                          

                                       

猜你喜欢

转载自blog.csdn.net/weixin_43931625/article/details/119942064