springboot 接口防刷
*****************
接口防刷
刷接口造成的影响
循环访问接口,会使流量暴增,影响网站的正常对外服务
如果短信验证、支付接口遭到恶意访问,还会造成企业损失
接口防刷措施
限制ip访问频率与并发数,可用nignx进行限制
limit_req_zone $binary_remote_addr zone=one:10m rate=20r/s; #单个ip每秒请求数
limit_conn_zone $binary_remote_addr zone=addr:10m; #单个ip每秒并发数
接口添加验证码,如短信接口发送短信前先使用验证码验证
重要接口在用户认证后才能访问,非认证用户不能访问
后端对接口限流,限制同一用户访问频率,可使用guava、Redisson提供的限流工具
*****************
示例:后端接口限流
***********
配置文件
application.yml
spring:
redis:
host: 192.168.57.120
port: 6379
***********
dto 层
ResponseResult
@Data
public class ResponseResult<T> {
private String code;
private String status;
private String message;
private T data;
}
***********
annotation 层
RateLimiter
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
int userLimit() default 5;
int ipLimit() default 10;
}
***********
interceptor 层
CustomInterceptor
@Configuration
public class CustomInterceptor implements HandlerInterceptor {
@Resource
private RedissonClient client;
private final ConcurrentHashMap<String,RRateLimiter> map = new ConcurrentHashMap<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod){
Method method = ((HandlerMethod) handler).getMethod();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();
String path=request.getServletPath();
if (method.isAnnotationPresent(RateLimiter.class)){
RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);
RRateLimiter limiter;
if (userName!=null && !userName.equals("anonymousUser")){
int userLimit = rateLimiter.userLimit();
String key=userName+path+"user-rate-limiter";
if (!map.containsKey(key)){
limiter = client.getRateLimiter(key);
limiter.setRate(RateType.PER_CLIENT,userLimit,1, RateIntervalUnit.MINUTES);
map.put(key,limiter);
}else {
limiter = map.get(key);
}
}else {
int ipLimiter = rateLimiter.ipLimit();
String remoteAddr = request.getRemoteAddr();
String key=remoteAddr+path+"ip-rate-limiter";
if (!map.containsKey(key)){
limiter = client.getRateLimiter(key);
limiter.setRate(RateType.PER_CLIENT,ipLimiter,1, RateIntervalUnit.MINUTES);
map.put(key,limiter);
}else {
limiter = map.get(key);
}
}
if (limiter.tryAcquire()){
return true;
}else {
throw new RuntimeException("请稍后重试");
}
}
}
return true;
}
}
***********
service 层
CustomUserService
@Service
public class CustomUserService implements UserDetailsService {
@Resource
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return new User("gtlx",passwordEncoder.encode("123456"),
Collections.singletonList(new SimpleGrantedAuthority("user")));
}
}
***********
config 层
DataConfig
@Configuration
public class DataConfig {
@Resource
private RedisProperties redisProperties;
@Bean
public RedissonClient intiRedissonClient(){
Config config=new Config();
config.useSingleServer()
.setAddress("redis://"+redisProperties.getHost()+":"+redisProperties.getPort());
return Redisson.create(config);
}
}
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private CustomInterceptor customInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor);
}
}
WebSecurityConfig
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
protected UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().and().authorizeRequests()
.antMatchers("/hello").authenticated()
.anyRequest().permitAll();
}
@Bean
public PasswordEncoder initPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
***********
controller 层
HelloController
@RestController
public class HelloController {
@RateLimiter(userLimit = 5)
@RequestMapping("/hello")
public ResponseResult<String> hello(){
ResponseResult<String> result=new ResponseResult<>();
result.setCode("000000");
result.setStatus("success");
result.setData("hello");
return result;
}
@RateLimiter()
@RequestMapping("/hello2")
public ResponseResult<String> hello2(){
ResponseResult<String> result=new ResponseResult<>();
result.setCode("000000");
result.setStatus("success");
result.setData("hello2");
return result;
}
@ExceptionHandler
public ResponseResult<String> handleError(Exception e){
ResponseResult<String> result=new ResponseResult<>();
result.setCode("111111");
result.setStatus("error");
result.setMessage(e.getMessage());
return result;
}
}
*****************
使用测试
localhost:8080/hello
认证通过后,输出
连续点击5次,输出
localhost:8080/hello2(重启应用)
连续点击10次(ip限流),输出