接口安全----接口防刷(拦截器)

私有:需要企业内部资源共享 内部接口

公有:短信平台,天气预报。。。。
接口安全要求:

1、防伪装攻击
 
处理方式:接口防刷
 
出现情况:公共网络环境中,第三方有意或者恶意调用我们的接口
 
2、防篡改攻击
 
处理方式:签名机制
 
出现情况:请求头/查询字符串/内容 在传输中来修改其内容
 
3、防重放攻击
 
处理方式:接口时效性
 
出现情况:请求被截获,稍后被重放或多次重放
 
4、防数据信息泄露
 
处理方式:接口加密(对称加解密)
 
出现情况:截获用户登录请求,主要是截获账号密码

防刷逻辑分析
需求:接口防刷

核心:服务器无法知道请求是正常的还是攻击性的

解决:设置接口访问的频率(单位时间内访问次数)
核心技术点:
在这里插入图片描述
可以采用:过滤器/拦截器 ------> 拦截所有接口,对所有暴露的接口作防刷操作

 
/**
思路:
1、设计Redis Key 临时 有效期1分钟 允许10次
KEY: URL:IP    --->     拼接的key   
value:用户访问次数
2、设置拦截器,拦截需要访问的接口URL
3:拦截逻辑
        3.1>拦截url,拼接key查询redis中是否存在
        3.2>如果不存在    url:ip 10     redist操作关键字:setnx 
        3.3>如果存在      url:ip                         derc 
        3.4>如果次数减到0,拦截返回:请勿频繁访问 拓展:加载入黑名单
        3.5>其他情况直接放行。
*/

接口:

/*
安全防护接口
 */
public interface ISecurityRedisService {
    boolean isAllowBrush(String key);
}

实现类:

@Service
public class SecurityRedisServiceImpl implements ISecurityRedisService {
 
    @Autowired
    private StringRedisTemplate template;
 
    @Override
    public boolean isAllowBrush(String key) {
        //等价于setnx
        template.opsForValue().setIfAbsent(key, "10", RedisKeys.BRUSH_PROOF.getTime(), TimeUnit.SECONDS);
 
        Long decrement = template.opsForValue().decrement(key);
 
        return decrement >= 0;
    }
}

启动配置类:

 
@Configuration
public class WebConfig implements WebMvcConfigurer{
    
 
   //防刷拦截
    @Bean
    public BrushProofInterceptor brushProofInterceptor(){
        return new BrushProofInterceptor();
    }
     
    //拦截器的注册-------------------------------------
    @Bean
    public CheckLoginInterceptor checkLoginInterceptor(){
        return new CheckLoginInterceptor();
    }
    //配置拦截的规则
 
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(checkLoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/swagger-ui.html");//哪些拦截
                //放行资源
//                .excludePathPatterns("/users/checkPhone")
//                .excludePathPatterns("/users/sendVerifyCode")
//                .excludePathPatterns("/users/login")
//                .excludePathPatterns("/users/regist");
        
        registry.addInterceptor(brushProofInterceptor()).addPathPatterns("/**");
    }
}

工具类:

 
/*
    请求工具类,获取信息
 */
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;
    }
}

RedisKey:
定义常量,拼接Key

public enum RedisKeys {
    //API接口防刷
    BRUSH_PROOF("brush_proof",-1L);
 
    private String prefix; //key的前缀
 
    private Long time; //约定的时效 单位秒
 
    private RedisKeys(String prefix, Long time) {
        this.prefix = prefix;
        this.time = time;
    }
 
 
    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();
    }
}

拦截器:

 
/*
防刷拦截
 */
public class BrushProofInterceptor implements HandlerInterceptor {
 
    @Autowired
    private ISecurityRedisService securityRedisService;
 
 
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
 
        //请求接口URL     Http://localhost:8088/xxx/xxx
        String url = request.getRequestURI().substring(1);
        //IP
        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;
    }
}

测试:

 
@RestController
public class TestController {
    @GetMapping("/test")
    public Object test(){
        return JsonResult.success("访问的请求进来了");
    }
}

结果:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值