개발자 되어버리기
SpringBoot 트래픽 제한하기 본문
한번에 많은 요청이 오거나 혹은 문자인증을 하거나 또는 외부로부터 공격이 왔을 경우
내부의 자원을 보호하기 위해 대비책을 세워두면 좋습니다.
이번 포스팅에서는 Bucket4j를 이용하여 트래픽을 제한하는 포스팅을 해보고자 합니다.
// 대역폭 제한
implementation 'com.giffing.bucket4j.spring.boot.starter:bucket4j-spring-boot-starter:0.2.0'
위와 같이 build.gradle에 라이브러리를 추가 해줍니다.
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PricingPlanService {
private final Map<String, Bucket> cache = new ConcurrentHashMap<>();
private String getHost(HttpServletRequest httpServletRequest){
return httpServletRequest.getHeader("Host");
}
/* ---------------------- 접속 제한! ----------------- */
public Bucket resolveBucket(HttpServletRequest httpServletRequest) {
return cache.computeIfAbsent(getHost(httpServletRequest), this::newBucket);
}
private Bucket newBucket(String apiKey) {
return Bucket4j.builder()
// 10개의 클라이언트가 10초에 1000개씩 보낼 수 있는 대역폭
.addLimit(Bandwidth.classic(1000, Refill.intervally(10, Duration.ofSeconds(10))))
.build();
}
/* ---------------------- sms 문자 제한! ----------------- */
public Bucket smsBucket(HttpServletRequest httpServletRequest) {
return cache.computeIfAbsent(getHost(httpServletRequest), this::newSmsBucket);
}
private Bucket newSmsBucket(String apiKey){
return Bucket4j.builder()
// 2개의 클라이언트가 30초에 10개씩 보낼 수 있는 대역폭
.addLimit(Bandwidth.classic(10, Refill.intervally(2, Duration.ofSeconds(30))))
.build();
}
}
크게 주석으로 구분해주시면 편합니다. 접속을 제한할 때 쓸 소스와 문자등에 제한할 소스코드 입니다.
이제 요청이 들어오고 저희가 흔히 작성하는 @GetMapping 에 매핑되기 전에! 트래픽을 인터셉트 하기 위한
HttpInterceptor를 만들게 됩니다.
import com.spring.service.PricingPlanService;
import io.github.bucket4j.Bucket;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Log4j2
public class HttpInterceptor extends HandlerInterceptorAdapter {
PricingPlanService pricingPlanService = new PricingPlanService();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
Bucket bucket = pricingPlanService.resolveBucket(request);
log.info("================ Before Method");
log.info("접속 ip 주소 '{}'", request.getRemoteAddr());
log.info(request.getRemoteAddr());
if (bucket.tryConsume(1)) { // 1개 사용 요청
// 초과하지 않음
log.info("초과 안함");
return true;
} else {
// 제한 초과
log.info("{} 트래픽 초과!!!", request.getRemoteAddr());
return false;
}
}
@Override
public void postHandle( HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) {
log.info("================ Method Executed");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
log.info("================ Method Completed");
}
}
prehandle - 요청이 실제로 mapping되기 전에 일어납니다.
postHandle - mapping 코드가 실행될 때 같이 실행 됩니다.
afterCompletion - 메소드가 실행된 이후에 실행 됩니다.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
HttpInterceptor httpInterceptor = new HttpInterceptor();
registry.addInterceptor(httpInterceptor);
}
}
이제 저희가 만든 인터셉터를 웹 설정값에 추가해줘야 합니다.
위와같이 인터셉터를 추가해줍니다.
이후에 스프링부트를 실행하고 웹사이트를 접속해보면 로그를 확인할 수 있습니다.
실제로 트래픽이 초과하는지 확인을 해보겠습니다. 확인의 편의성을 위해 트래픽 제한을 낮추었습니다.
실제로 10번정도 새로고침해보면 트래픽을 초과하는것을 확인할 수 있습니다!
'개발 > Spring_Boot' 카테고리의 다른 글
Springboot (AOP) 메소드별 실행시간 구하기 (0) | 2021.01.04 |
---|---|
Springboot 토큰 걸러내기 (0) | 2020.12.13 |
SpringBoot에서 Docker Redis를 이용해 jwt 로그아웃 처리하기 (0) | 2020.12.05 |
네이버 메일로 SMTP 사용하기 (0) | 2020.11.14 |
Springboot + NAVER S.E.N.S 보내기 (V2 헤더 세팅) (8) | 2020.11.14 |