分析

  • 避免同一个客户端短时间内重复访问一次接口过多次数
  • 此演示,一个客户端访问一个接口,1分钟内访问超过10次则被禁止访问,1分钟过后即可继续访问~
    在这里插入图片描述

准备工作

RequestUtil

  • 获取ip地址的工具类
public class RequestUtil {
    public static String getIPAddress() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); ;
        String ip = null;
        //X-Forwarded-For:Squid 服务代理
        String ipAddresses = request.getHeader("X-Forwarded-For");
        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //Proxy-Client-IP:apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //WL-Proxy-Client-IP:weblogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //HTTP_CLIENT_IP:有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //X-Real-IP:nginx服务代理
            ipAddresses = request.getHeader("X-Real-IP");
        }

        //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
        if (ipAddresses != null && ipAddresses.length() != 0) {
            ip = ipAddresses.split(",")[0];
        }

        //还是不能获取到,最后再通过request.getRemoteAddr();获取
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

RedisKeys管理枚举

  • 设定此key的时效时长是一分钟,所以当用户访问同一个接口过多,则限制1分钟不能再访问
//redis的key的管理类
//约定: 一个redis key 映射一个枚举实例
@Getter
public enum RedisKeys {

    BRUSH_PROOF("brush_proof",60L),

    private String prefix;//key的前缀
    private Long time;//key有效时间,单位是s

     RedisKeys(String prefix, Long time) {
        this.prefix = prefix;
        this.time = time;
    }

    //    拼接出完整redis的key
    public String join(String... values) {
        StringBuilder sb = new StringBuilder(80);
        sb.append(this.prefix);
        for (String value : values) {
            sb.append(":").append(value);
        }
        return sb.toString();
    }

}

防刷拦截器

  • 注入ISecurityRedisService接口,调用方法,操作redis
  • 将访问的接口和访问的客户端唯一的ip地址拼接作为redis的key,这样就能锁定一个客户端的操作次数
  • 若此客户端
  • request.getRequestURI().substring(1); 获取访问的url,除去第一个字符/
  • RequestUtil.getIPAddress();获取访问的客户端的ip地址
/**
 * 防刷拦截器
 */
public class BrushProofInterceptor implements HandlerInterceptor {

    @Autowired
    private ISecurityRedisService securityRedisService;

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

        if(!(handler instanceof HandlerMethod)){
            return  true;
        }

        //防刷验证
        String url = request.getRequestURI().substring(1);
        String ip = RequestUtil.getIPAddress();
        String key = RedisKeys.BRUSH_PROOF.join(url, ip);
        if(!securityRedisService.isAllowBrush(key)){
            response.setContentType("text/json;charset=UTF-8");
            response.getWriter().write(JSON.toJSONString(JsonResult.error(500, "请勿频繁访问","谢谢咯")));
            return false;
        }
        return true;
    }
}

拦截器调用的业务层实现方法

  • 先进行key的添加,value值为10 (若有此key,则不添加)
  • 若同一个key参数传过来,每次执行此方法则将对应value值减1,执行10次以后则此key的value值小于0,返回false(不放行,禁止此客户端再次访问)
  • 使用的setIfAbsent方法是重点,若有此key则不进行时效时长的刷新,还是继续时效性倒计时,时效性没了,又可以继续访问接口了
@Service
public class SecurityRedisServiceImpl implements ISecurityRedisService {

    @Autowired
    private StringRedisTemplate template;

    @Override
    public boolean isAllowBrush(String key) {
        //如果有不做 任何操作,如果没有添加
        template.opsForValue().setIfAbsent(key, "10", RedisKeys.BRUSH_PROOF.getTime(), TimeUnit.SECONDS);
        Long decrement = template.opsForValue().decrement(key);
        return decrement >= 0;//减一
    }

}

配置拦截器

  • 配置拦截器的配置类,要实现WebMvcConfigurer 接口
  • 这里可配多种拦截器,此演示只演示配置此配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {

   //拦截器配置
    @Bean
    public BrushProofInterceptor brushProofInterceptor() {
        return new BrushProofInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(brushProofInterceptor())
                .addPathPatterns("/**");
    }
    }
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐