接口被刷了,怎么办?
# 前言
在面试时,经常会被问一个问题:如何防止别人恶意刷接口?
这是一个非常有意思的问题,防范措施挺多的。今天这篇文章专门跟大家一起聊聊,希望对你会有所帮助。
# 防火墙
防火墙是一个网络安全产品,它是由软件和硬件设备组合而成,在内网和外网之间、专用网与公共网之间的一种保护屏障。在计算机网络的内网和外网之间构建一道相对隔离的保护屏障,以达到保护资料的目的。它是一种隔离技术,可以防止非法用户的侵入,同时及时发现并处理计算机网络运行时潜在的安全风险、数据传输等问题,确保计算机网络正常运行。
# 防火墙的作用
1、防止病毒 虽然防火墙本身不充当防病毒软件,但它们通过确保只有授权的数据流经网络,确实有助于防止病毒的安装。
2、防止访问不安全内容 有些防火墙不仅关注进入网络的流量,它们还可以防止未经授权访问危险的网站。防火墙还可以阻止访问已知会在访问者的计算机上安装恶意软件和病毒的恶意站点。
3、阻止未经授权的访问 如果公司因为网络攻击而瘫痪,对于大多数中小型公司来说可能是毁灭性的,任何未经授权访问系统文件都可能导致客户信息泄露、重要数据丢失,并可能危及更多安全功能。
4、无效数据包
防火墙可以识别和过滤掉无效的数据包,如错误的 IP 地址、伪造的数据包和无法识别的协议等。
5、恶意流量攻击
检测和防止恶意流量攻击,如过滤掉带有恶意载荷的数据包和防止被黑客利用的端口。
6、DOS 和 DDOS 攻击
防火墙可以检测和阻止 DOS 和 DDOS 攻击,如阻止大量 TCP/UDP 连接、IP 地址过滤和流量限制等。
# 防火墙的应用场景
1、出口网关 出口网关是比较常见的使用场景,使用防火墙在互联网出口处,提供NAT、路由、端口映射等功能。
2、安全域边界防护 专网或大型网络内对各个不同安全域进行隔离防护。
3、IPSEC VPN 两台或两台设备之间使用IPSEC VPN进行互联,多用于总部与分支网络使用。
# 验证码
在需要保护的接口中添加验证码验证,要求用户在访问前先进行验证码验证,以确认其为真实用户。
常用的验证码有图形验证码、移动滑块形式的图形验证码、短信验证码,但是短信验证码要做限制,如果不限制,被恶意调用,可能会产生昂贵的费用。
# 用户身份认证和授权
用户在访问API接口前进行身份认证,并根据用户的权限进行授权,只允许有权限的用户访问特定接口。
# IP白名单
对于项目中一些非常重要的接口(比如开通会员的接口),将调用方的服务所在ip添加至ip白名单中(ip白名单可以是一个配置文件,或者直接存储在数据库中),这样即使开通会员接口地址和请求参数被泄露了,调用者的ip不在白名单上,请求开通会员接口会直接失败。
public class IPInterceptor implements HandlerInterceptor {
private static final List<String> ALLOWED_IPS = Arrays.asList("127.0.0.1");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String ipAddress = request.getRemoteAddr();
if (ALLOWED_IPS.contains(ipAddress)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("Access denied");
return false;
}
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 数据加密
敏感数据进行加密传输,使用HTTPS协议保证数据传输的安全性。
HTTPS协议是在HTTP协议的基础上,添加了加密机制:
SSL:它是Secure Socket Layer的缩写, 表示安全套接层。TLS:它是Transport Layer Security的缩写,表示传输层安全。HTTPS = HTTP + 加密 + 认证 + 完整性保护。
为了安全性考虑,我们的接口如果能使用HTTPS协议,尽量少使用HTTP协议。
# 接口访问频率限制
设置访问频率限制,例如每分钟/每小时/每天只允许一定次数的请求,超出限制则返回错误信息或封禁IP。
参考 优雅的接口防刷处理方案
# 限流
可以通过注解+AOP方式实现。
定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AccessInterceptor {
/** 用哪个字段作为拦截标识,未配置则默认走全部 */
String key() default "all";
/** 限制频次(每秒请求次数) */
double permitsPerSecond();
/** 黑名单拦截(多少次限制后加入黑名单)0 不限制 */
double blacklistCount() default 0;
/** 拦截后的执行方法 */
String fallbackMethod();
}
@Pointcut("@annotation(cn.bugstack.xfg.dev.tech.annotation.AccessInterceptor)")
public void aopPoint() {
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
AOP拦截
@Slf4j
@Aspect
public class RateLimiterAOP {
// 个人限频记录1分钟
private final Cache<String, RateLimiter> loginRecord = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
// 个人限频黑名单24h - 自身的分布式业务场景,可以记录到 Redis 中
private final Cache<String, Long> blacklist = CacheBuilder.newBuilder()
.expireAfterWrite(24, TimeUnit.HOURS)
.build();
@Pointcut("@annotation(cn.bugstack.xfg.dev.tech.annotation.AccessInterceptor)")
public void aopPoint() {
}
@Around("aopPoint() && @annotation(accessInterceptor)")
public Object doRouter(ProceedingJoinPoint jp, AccessInterceptor accessInterceptor) throws Throwable {
String key = accessInterceptor.key();
if (StringUtils.isBlank(key)) {
throw new RuntimeException("annotation RateLimiter uId is null!");
}
// 获取拦截字段
String keyAttr = getAttrValue(key, jp.getArgs());
log.info("aop attr {}", keyAttr);
// 黑名单拦截
if (!"all".equals(keyAttr) && accessInterceptor.blacklistCount() != 0 && null != blacklist.getIfPresent(keyAttr) && blacklist.getIfPresent(keyAttr) > accessInterceptor.blacklistCount()) {
log.info("限流-黑名单拦截(24h):{}", keyAttr);
return fallbackMethodResult(jp, accessInterceptor.fallbackMethod());
}
// 获取限流 -> Guava 缓存1分钟
RateLimiter rateLimiter = loginRecord.getIfPresent(keyAttr);
if (null == rateLimiter) {
rateLimiter = RateLimiter.create(accessInterceptor.permitsPerSecond());
loginRecord.put(keyAttr, rateLimiter);
}
// 限流拦截
if (!rateLimiter.tryAcquire()) {
if (accessInterceptor.blacklistCount() != 0) {
if (null == blacklist.getIfPresent(keyAttr)) {
blacklist.put(keyAttr, 1L);
} else {
blacklist.put(keyAttr, blacklist.getIfPresent(keyAttr) + 1L);
}
}
log.info("限流-超频次拦截:{}", keyAttr);
return fallbackMethodResult(jp, accessInterceptor.fallbackMethod());
}
// 返回结果
return jp.proceed();
}
/**
* 调用用户配置的回调方法,当拦截后,返回回调结果。
*/
private Object fallbackMethodResult(JoinPoint jp, String fallbackMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Signature sig = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());
return method.invoke(jp.getThis(), jp.getArgs());
}
private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
Signature sig = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
}
/**
* 实际根据自身业务调整,主要是为了获取通过某个值做拦截
*/
public String getAttrValue(String attr, Object[] args) {
if (args[0] instanceof String) {
return args[0].toString();
}
String filedValue = null;
for (Object arg : args) {
try {
if (StringUtils.isNotBlank(filedValue)) {
break;
}
// filedValue = BeanUtils.getProperty(arg, attr);
// fix: 使用lombok时,uId这种字段的get方法与idea生成的get方法不同,会导致获取不到属性值,改成反射获取解决
filedValue = String.valueOf(this.getValueByName(arg, attr));
} catch (Exception e) {
log.error("获取路由属性值失败 attr:{}", attr, e);
}
}
return filedValue;
}
/**
* 获取对象的特定属性值
*
* @param item 对象
* @param name 属性名
* @return 属性值
* @author tang
*/
private Object getValueByName(Object item, String name) {
try {
Field field = getFieldByName(item, name);
if (field == null) {
return null;
}
field.setAccessible(true);
Object o = field.get(item);
field.setAccessible(false);
return o;
} catch (IllegalAccessException e) {
return null;
}
}
/**
* 根据名称获取方法,该方法同时兼顾继承类获取父类的属性
*
* @param item 对象
* @param name 属性名
* @return 该属性对应方法
* @author tang
*/
private Field getFieldByName(Object item, String name) {
try {
Field field;
try {
field = item.getClass().getDeclaredField(name);
} catch (NoSuchFieldException e) {
field = item.getClass().getSuperclass().getDeclaredField(name);
}
return field;
} catch (NoSuchFieldException e) {
return null;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# 使用API网关
在API接口和客户端之间引入API网关,对请求进行过滤、鉴权、限流等操作,保护后端API接口的安全。
# 日志监控
监控API接口的访问日志,及时发现异常请求,例如某个IP频繁请求同一接口,及时采取相应的安全措施。
有专门的程序,统计用户接口的调用情况,如果发现有突增的流量,会自动发短信或者邮件提醒。
有了监控之后,我们可以及时发现异常的用户请求,可以进行人工干预处理。