如何实现接口防刷(接口防盗刷)
qiyuwang 2024-11-17 15:12 20 浏览 0 评论
1 前言
我们在浏览网站后台的时候,假如我们频繁请求,那么网站会提示 “请勿重复提交” 的字样,那么这个功能究竟有什么用呢,又是如何实现的呢?
其实这就是接口防刷的一种处理方式,通过在一定时间内限制同一用户对同一个接口的请求次数,其目的是为了防止恶意访问导致服务器和数据库的压力增大,也可以防止用户重复提交。
1.1 如何防止后端接口被刷
要防止Java后端接口被刷,可以采取以下一些措施:
- 限制请求频率:通过设置一定的时间间隔或请求次数限制,来防止接口被频繁调用。例如,可以限制每分钟最多只能请求10次接口,超过则返回错误。
- 校验请求参数:对于GET和POST请求,需要对请求参数进行校验,包括但不限于参数的格式、长度、范围等,确保请求的合法性。
- 添加验证码:在接口中添加验证码,可以防止恶意请求。验证码可以是图片验证码、短信验证码、语音验证码等。
- 记录请求日志:记录每个请求的详细信息,包括时间、IP、请求方法、请求参数等,以便于排查异常请求和追踪恶意攻击者。
- 使用安全头信息:在响应中设置安全头信息,例如X-Frame-Options、Content-Security-Policy等,来增强接口的安全性。
- 使用HTTPS协议:通过使用HTTPS协议来加密数据传输,防止接口数据被截获。
- 部署防火墙:使用防火墙来限制对接口的访问,只允许特定的IP或IP段进行访问,或者只允许特定的HTTP方法进行访问等。(工作的时候可以采用Nginx来做接口的限流,也可以用Nginx做白名单和黑名单),开源的Web防火墙可以使用。
- 使用限流算法:通过使用限流算法,例如令牌桶、漏桶等,来限制接口的QPS和并发连接数,防止接口被恶意刷取。
综上所述,以上是一些常用的Java后端接口防刷措施,可以根据具体场景和需求进行选择和组合使用。
1.2 在Java后端接口防刷方面还有哪些建议
除了上述提到的措施,以下是一些额外的建议:
- 使用令牌系统:令牌系统可以有效地防止接口被刷。在客户端请求接口时,需要先进行身份验证,服务端会返回一个令牌,客户端在下一次请求时需要带上这个令牌。服务端会对每个令牌进行计数和超时处理,从而实现对频繁请求的限制。不是每一个接口在访问的时候都是需要令牌,不需要登录就可以访问的接口。
- IP限流:通过在服务端记录客户端的IP地址,并设置请求限制,当客户端在短时间内请求次数过多时,服务端会拒绝请求。这种方式可以有效防止恶意刷接口的行为。
- 定义IP过滤器:在使用Redis的情况下,可以定义IP过滤器,计算指定IP请求速率。在过滤器中拦截所有请求,每个IP对于单独接口在访问周期内超出限制之后将当前IP限制一段时间。
- 基于IP过滤器统计接口访问次数:在IP过滤器中借助Redis计算接口访问次数,每天同步一次,这对于后面的服务扩展,接口限流等都很有好处。
- 使用分布式锁:对于并发量较大的接口,可以使用分布式锁来限制并发数量,防止单个接口被大量请求。
- 定期检查和更新接口安全性:定期检查接口的安全性,例如检查接口是否被恶意攻击,是否有漏洞等,并及时进行修复。
- 做好日志记录和分析:记录每个接口请求的详细信息,包括时间、IP、请求方法、请求参数等,通过日志分析来发现异常请求和追踪恶意攻击者。
- 使用专业的安全工具:使用专业的安全工具,例如WAF(Web Application Firewall),可以有效地保护后端接口的安全性。
综上所述,Java后端接口防刷需要综合考虑多种措施,包括但不限于限制请求频率、校验请求参数、添加验证码、记录请求日志、使用安全头信息、使用HTTPS协议、部署防火墙、使用限流算法等。同时,也需要做好日志记录和分析、使用专业的安全工具等措施来提升接口安全性。
1.3 有哪些前端安全性措施可以保障接口安全性
前端安全性措施是保障接口安全性的重要环节,以下是一些常见的前端安全性措施:
- 使用HTTPS协议:通过使用HTTPS协议来加密数据传输,可以防止数据在传输过程中被窃取或被篡改。
- Token授权认证:在身份验证过程中,使用Token授权认证可以防止未授权用户获取数据。在前端请求接口时,需要将Token作为请求头信息的一部分进行传递,服务端通过验证Token来确认请求的合法性。
- 时间戳超时机制:在前端发送请求时,可以附带一个时间戳,服务端根据时间戳来判断请求是否超时。如果请求超时,服务端可以返回错误信息或者直接拒绝请求。
- URL签名:通过URL签名可以防止请求参数被篡改。前端将请求参数进行签名,并将签名作为URL的一部分传递给服务端,服务端使用相同的算法和密钥对URL中的签名进行验证,以确定请求的合法性。
- 防重复:防重复可以防止接口被第二次请求或者被采集。前端可以通过添加时间戳、UUID等参数来实现防重复,服务端通过对时间戳、UUID等参数的校验来防止重复请求。
- 输入验证:在前端对用户输入的数据进行验证,可以避免非法数据被传入。例如,对于用户名、密码等敏感信息,前端可以通过正则表达式或者其他验证方式来验证用户输入的合法性。
- 防止SQL注入和XSS攻击:前端需要对用户输入的数据进行适当的转义和过滤,以防止SQL注入和XSS攻击。对于动态生成的SQL语句,前端可以使用参数化查询或者预编译语句来避免SQL注入攻击;对于输出到浏览器的内容,前端可以使用转义函数来避免XSS攻击。
- 安全存储Token:在前端存储Token时,需要采取安全措施来保护Token的安全性。例如,可以将Token存储在HTTP Only的cookie中,以防止通过JavaScript等手段获取Token。
- 日志记录和异常处理:前端需要记录请求的IP地址、请求的接口、请求的时间以及请求的用户信息等,以便于监控和溯源。同时,需要设置异常处理机制,避免程序崩溃或被恶意攻击。
综上所述,前端安全性措施包括使用HTTPS协议、Token授权认证、时间戳超时机制、URL签名、防重复、输入验证、防止SQL注入和XSS攻击、安全存储Token以及日志记录和异常处理等。这些措施可以有效地保护接口安全性,并提高应用程序的整体安全性。
Nginx也可以做限流
1.4 防重复技术的工作原理
防重复技术的工作原理是防止攻击者通过重复发送已接收的请求来欺骗系统。防重复攻击的核心在于,防止黑客抓取请求报文,从而进行重播攻击。为此,需要在客户端和服务器端采取一些措施:
- 客户端:在客户端,可以采取一些措施来防止请求被重复发送。例如,可以使用时间戳、随机数、流水号等参数来标识每个请求,并将这些参数作为请求报文的一部分发送给服务器。服务器端会对这些参数进行验证,判断是否为一次有效的请求。
- 服务端:在服务器端,也需要采取一些措施来防止重放攻击。例如,可以在服务器端设置一个专门用于防重复的缓存区,将已接收的请求报文存储在该缓存区中。当服务器接收到一个新的请求报文时,会将其与缓存区中的数据进行比对,如果该请求报文已经在缓存区中存在,则判定为重放攻击,并拒绝该请求。
另外,对于一些敏感操作,还可以在服务器端实现防重复功能。例如,可以在服务器端设置一个防重复令牌,该令牌在生成时会关联到当前会话和用户信息。当服务器接收到一个请求报文时,会验证该令牌是否存在,如果存在且与当前会话和用户信息匹配,则判定为有效请求;否则,判定为重放攻击,并拒绝该请求。
需要注意的是,防重复技术并不能完全防止所有的重放攻击,因为攻击者可以伪造各种请求报文来绕过防重复机制。因此,还需要结合其他安全措施来提高系统的安全性。
10.2 思路分析
接口防刷有很多种实现思路,例如:拦截器/AOP+Redis、拦截器/AOP+本地缓存、前端限制等等很多种实现思路,在这里我们来讲一下 拦截器+Redis 的实现方式。
其原理就是 在接口请求前由拦截器拦截下来,然后去 redis 中查询是否已经存在请求了,如果不存在则将请求缓存,若已经存在则返回异常。具体可以参考下图:
10.3 具体实现
3.0 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.39</version>
</dependency>
3.1 AjaxResult
package com.chenxin.iab.util;
import java.util.HashMap;
public class AjaxResult extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/* 状态码 */
public static final String CODE_TAG = "code";
/* 返回内容 */
public static final String MSG_TAG = "msg";
/* 数据对象 */
public static final String DATA_TAG = "data";
/**
* 初始化一个AjaxResult 对象
*/
public AjaxResult() {
}
/**
* 初始化一个AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (!(data == null)) {
super.put(DATA_TAG, data);
}
}
/**
* 返回默认成功消息
*
* @return 成功消息
*/
public static AjaxResult success() {
return AjaxResult.success("操作成功");
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg) {
return AjaxResult.success(msg, null);
}
/**
* 返回成功数据
* @param data 数据对象
* @return
*/
public static AjaxResult success(Object data) {
return AjaxResult.success("操作成功", data);
}
/**
* 初始化一个返回成功消息AjaxResult对象
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data) {
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}
/**
* 返回默认错误消息
*
* @return
*/
public static AjaxResult error() {
return AjaxResult.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(String msg) {
return AjaxResult.error(msg, null);
}
/**
* 返回错误数据
*
* @param code 状态码
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(int code, String msg) {
return new AjaxResult(code, msg, null);
}
/**
* 初始化一个返回错误消息AjaxResult对象
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult error(String msg, Object data) {
return new AjaxResult(HttpStatus.ERROR, msg, data);
}
}
3.2 HttpStatus
package com.chenxin.iab.util;
public class HttpStatus {
/**
* 操作成功
*/
public static final int SUCCESS = 200;
/**
* 对象创建成功
*/
public static final int CREATED = 201;
/**
* 请求已经被接受
*/
public static final int ACCEPTED = 202;
/**
* 操作已经执行成功,但是没有返回数据
*/
public static final int NO_CONTENT = 204;
/**
* 资源已被移除
*/
public static final int MOVED_PERM = 301;
/**
* 重定向
*/
public static final int SEE_OTHER = 303;
/**
* 资源未被修改
*/
public static final int NOT_MODIFIED = 304;
/**
* 参数列表错误(缺少、格式不匹配)
*/
public static final int BAD_REQUEST = 400;
/**
* 未授权
*/
public static final int UNAUTHORIZED = 401;
/**
* 访问受限,授权过期
*/
public static final int FORBIDDEN = 403;
/**
* 资源,服务未找到
*/
public static final int NOT_FOUND = 404;
/**
* 未被允许http方法
*/
public static final int BAD_METHOD = 405;
/**
* 资源冲突,或者资源被锁
*/
public static final int CONFLICT = 409;
/**
* 不支持媒体类型数据
*/
public static final int UNSUPPORTED_TYPE = 415;
/**
* 系统内部错误
*/
public static final int ERROR = 500;
/**
* 未实现相应接口
*/
public static final int NOT_IMPLEMENTED = 501;
}
3.3 RedisUtils
package com.chenxin.iab.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/****************** common start ****************/
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
/****************** common end ****************/
/****************** String start ****************/
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) throws MyRedidsException {
if (delta < 0) {
throw new MyRedidsException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) throws MyRedidsException {
if (delta < 0) {
throw new MyRedidsException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/****************** String end ****************/
}
3.4 拦截器
package com.chenxin.iab.util;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
@Component
public class RepeatRequestIntercept extends HandlerInterceptorAdapter {
@Autowired
private RedisUtils redisUtils;
/**
* 限定时间 单位:秒
*/
private final int seconds = 1;
/**
* 限定请求次数
*/
private final int max = 1;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断请求是否为方法的请求
if (handler instanceof HandlerMethod) {
String key = request.getRemoteAddr() + "-" + request.getMethod() + "-" + request.getRequestURL();
Object requestCountObj = redisUtils.get(key);
if (Objects.isNull(requestCountObj)) {
//若为空则为第一次请求
redisUtils.set(key, 1, seconds);
} else {
response.setContentType("application/json;charset=utf-8");
ServletOutputStream os = response.getOutputStream();
AjaxResult result = AjaxResult.error(100, "请求已提交,请勿重复请求");
String jsonString = JSON.toJSONString(result);
os.write(jsonString.getBytes());
os.flush();
os.close();
return false;
}
}
return true;
}
}
3.5 拦截器注入到容器
package com.chenxin.iab.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RepeatRequestIntercept repeatRequestIntercept;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(repeatRequestIntercept);
}
}
3.6 自定义异常
public class MyRedidsException extends Exception {
private String msg;
public MyRedidsException(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "MyRedidsException{" +
"msg='" + msg + '\'' +
'}';
}
}
3.7 RedisConfig
package com.chenxin.iab.util;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, ?> getRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, ?> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setEnableTransactionSupport(true);
template.setConnectionFactory(factory);
return template;
}
}
3.8 Handler
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloHandler {
@GetMapping("/test")
public String test(){
return "SUCCESS";
}
}
相关推荐
- 在Word中分栏设置页码一页两个页码的技巧!
-
施老师:在正常情况下,Word文档中一页只会出现一个页码。但在某种情况下,比如说:用了分栏后,我们希望一页中出现两个页码,那应该如何实现呢?今天,就由宁双学好网施老师来为大家讲一下,利用域来实现一页两...
- 如何在关键时刻向上自荐(如何在关键时刻做出正确选择)
-
抓住机会,挺身而出有种时刻叫“关键时刻”,关键时刻,作为一个认为自己有能力的、训练有素的人,应该考虑挺身而出,甚至应该不考虑就挺身而出。...
- WPS Word:跨页的文档表格,快速调整为一页。#Excel
-
如何快速将跨页的文档表格调整为一页?需要根据两种情况分别处理。如果表格所有行的行高相同,调整为一页的方法有两种。第一种方法是将光标移动到表格内,然后将鼠标移动到表格右下角的方框处,按住鼠标左键向上拖动...
- word文档插入下一页分节符(word下一页分页符)
-
在word文档中,对文档页面进行分页是特别常见的操作,其中的下一页分节符也是用得比较多的,但是一些人不太清楚在哪里设置,也不知道它具体能实现的功能是什么。接下来看看如何在word文档中插入下一页分节符...
- word文档如何设置某一页纸张的方向
-
word文档页面方向有横向和纵向,纵向是默认的纸张方向,有时我们需要将页面设置为横向,或只设置其中某一页方向,应该怎么操作呢?一起来看看下面的详细介绍第一步:...
- word怎么单独设置一页为横向(word2019怎样设置单独一页为横向)
-
word里面其中一页可以改为横向的吗?经过实际操作发现是完全可以的。...
- Word如何设置分栏,如何一页内容同时显示一栏和两栏
-
我们使用Word文档,有时需要用到两栏的排版,甚至一页内容同时包含一栏和两栏的排版,这种格式怎么设置呢?具体步骤如下:首先是两栏排版的设置,直接点击Word文件上方工具栏【布局】,选择【分栏】下面的【...
- Word怎么分页?这三个方法可以帮到你
-
我们不仅可以利用Word编辑文档,还可以编辑文集呢。但是有时候会出现两个部分的文章长短不一,我们需要对文档进行分页处理。这样可以方便我们对文档进行其他操作。那么Word怎么分页呢?大家可以采用下面这...
- Word内容稍超一页,如何优化至单页打印?
-
如何将两页纸的内容,缩到一页打印呢?有时候一页纸多一点内容,我们完全可以缩一下,放到一页来打印。...
- [word] word 表格如何跨行显示表头、标题
-
word表格如何跨行显示表头、标题在Word中的表格如果过长的话,会跨行显示在另一页,如果想要在其它页面上也显示表头,更直观的查看数据。难道要一个个复制表头吗?当然不是,教你简单的方法操作设置Wo...
- Word表格跨页如何续上表?(word如何让表格跨页不断掉)
-
长文档的表格跨页时,你会发现页末空白太多了,这时要怎么调整?选中整张表格,右击【表格属性】,点击【行】选项,之后勾选【允许跨页断行】,点击确定即可解决空白问题。...
- Word怎么连续自动生成页码,操作步骤来了!
-
Word怎么连续自动生成页码,操作步骤来了!...
- word文档怎么把两页合并成一页内容?教你4种方法
-
word怎么把两页合并成一页?word怎么把两页合并成一页?用四种方法演示一下。·方法一:把这一个文档合并成一页,按ctrl加a全选文档,然后右键点击段落,弹出的界面行距改成固定值,磅值可以改小一点,...
- 如何将Word中的一页的纸张方向设置为横向?这里提供详细步骤
-
默认情况下,MicrosoftWord将页面定向为纵向视图。虽然这在大多数情况下都很好,但你可能拥有在横向视图中看起来更好的页面或页面组。以下是实现这一目标的两种方法。无论使用哪种方法,请注意,如果...
- Word横竖混排你会玩吗?(word横排竖排混合)
-
我们在用Word排版的时候,一般都是竖版格式,但偶尔会需要到一些特殊的版式要求,比如文档中插入的一个表格,横向的内容比较多,这时就需要用到横版,否则表格显示不全。这种横竖版混排的要求,在Word20...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- navicat无法连接mysql服务器 (65)
- 下横线怎么打 (71)
- flash插件怎么安装 (60)
- lol体验服怎么进 (66)
- ae插件怎么安装 (62)
- yum卸载 (75)
- .key文件 (63)
- cad一打开就致命错误是怎么回事 (61)
- rpm文件怎么安装 (66)
- linux取消挂载 (81)
- ie代理配置错误 (61)
- ajax error (67)
- centos7 重启网络 (67)
- centos6下载 (58)
- mysql 外网访问权限 (69)
- centos查看内核版本 (61)
- ps错误16 (66)
- nodejs读取json文件 (64)
- centos7 1810 (59)
- 加载com加载项时运行错误 (67)
- php打乱数组顺序 (68)
- cad安装失败怎么解决 (58)
- 因文件头错误而不能打开怎么解决 (68)
- js判断字符串为空 (62)
- centos查看端口 (64)