私有:需要企业内部资源共享 内部接口
公有:短信平台,天气预报。。。。
接口安全要求:
1、防伪装攻击
处理方式:接口防刷
出现情况:公共网络环境中,第三方有意或者恶意调用我们的接口
2、防篡改攻击
处理方式:签名机制
出现情况:请求头/查询字符串/内容 在传输中来修改其内容
3、防重放攻击
处理方式:接口时效性
出现情况:请求被截获,稍后被重放或多次重放
4、防数据信息泄露
处理方式:接口加密(对称加解密)
出现情况:截获用户登录请求,主要是截获账号密码
防刷逻辑分析
需求:接口防刷
核心:服务器无法知道请求是正常的还是攻击性的
解决:设置接口访问的频率(单位时间内访问次数)
核心技术点:
接口URL 频率 识别访问者请求IP
现有技术中------>Redis 限制有时效 接口的限制都是高效率的所以用Redis来缓存请求信息
key Value
brush_proof:ip:url 10 时效1分钟
可以采用:过滤器/拦截器 ------> 拦截所有接口,对所有暴露的接口作防刷操作
/**
思路:
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("访问的请求进来了");
}
}