Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
1 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
Tags
more
Archives
Today
Total
관리 메뉴

개발자 되버리기

SpringBoot 트래픽 제한하기 본문

개발/Spring_Boot

SpringBoot 트래픽 제한하기

구본익 2020. 12. 6. 01:33

한번에 많은 요청이 오거나 혹은 문자인증을 하거나 또는 외부로부터 공격이 왔을 경우

내부의 자원을 보호하기 위해 대비책을 세워두면 좋습니다.

이번 포스팅에서는 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초에 10개만 보내면 됩니다.

 

 

실제로 10번정도 새로고침해보면 트래픽을 초과하는것을 확인할 수 있습니다!

Comments