본문 바로가기
spring & boot/Spring & Spring Boot

[Spring] Gateway - 해외 IP 차단(필터링)

by lucas_owner 2025. 4. 2.

 

Spring Cloud Gateway - 해외 IP 차단

이전 포스팅에서 홈서버로 들어오는 Request 중 해외 IP 로 요청한 경우는 차단(Block) 했었다고 언급만 했었다. 

도메인과 Reverse Proxy Server 를 연결 한 이후부터 서버 IP 가 공격자들에게 노출되었는지 매시간 수많은 공격들이 들어오고 있던 상황이었다. (SQL Injection, Brute Force, IDOR(Insecure Direct Object Reference 등등 ....)

나의 서버의 경우 해외에서 수요가 있는것도 아니고, 국내에서만 유효하면 됬기에 해외 IP 차단을 계획했다. 

 

홈서버 아키텍처 확인하기

 

 

GeoIP2 라는 국가별 IP 를 확인할 수 있는 오픈소스솔루션을 사용할 예정이다.

Reverse  Proxy Server 에 실행되고 있는 Nginx 를 사용하는게 아키텍처면에서 좋아보이지만, 홈서버 글에서 봤듯 제한적인 리소스 때문에 Gateway 단에서 구분을 해주려고 한다.

 

Spring Cloud Gateway 안에서 GeoIP2 와 연동하여 해외 IP 차단을 진행할 계획이며 상황에 따라 Spring boot 와 같은 Application 에서 사용해도 된다.

 

 

1. MaxMind (GeoIP2)

https://www.maxmind.com/en/home

IP 정보를 담고있는 DB 를 받기 위해서는 maxmind 해당 사이트에 회원가입을 진행해야 한다. 

여기서 제공하는 DB 는 파일 형태로 제공되며, 우리는 해당 파일에서 IP 에 대한 정보를 얻어서 허용 or 차단 하는 방식이다. 

개인정보페이지

회원가입이후 마이페이지 내부에서 DB 를 다운로드 받을 수 있다. 

GeoIp2 < Download Files 안에서 확인 가능하다. 이때 Geoip2 에 "다운로드 파일" 부분이 보이지 않는다면 GeoIP 가입을 하면 보이게된다.

 

 

DB 리스트에서 아래로 내려서 GeoLite2 Country Gzip 파일을 다운로드 받도록 하자.

압축해제시 내부에 .mmdb 파일이 우리가 사용하게 될 실제 파일이다.

 

 

 

2. Spring Cloud Gateway - GeoIP2 연결

우선 GeoIP2 에서 제공하는 Dependency 를 추가해주도록 하자. 

implementation("com.maxmind.geoip2:geoip2:2.3.1") // geoIP2

2025.04.01 기준 가장 최신버전이고 Maven Repository 에 존재한다. 

 

GeoLite2-Country.mmdb 파일은 resource 안에 위치하거나, Project 외부에 위치 시켜 사용할 수 있다.

필자의 경우는 Local 테스트 전용으로 resource 안에 위치시켰고, 실제 운영환경은 서버내에 특정 경로에 위치 시켜주었다.

 

해당 mmdb 파일의 경우 매달 업데이트가 되며, 업데이트 파일을 다운로드 받아서 교체하는 방식이기때문에 외부에 위치시켜 적용하는게 좀더 좋은 방법 같다. 또한 DB 파일 변경시 resource 내부에 존재한다면 매번 새로 빌드하여 배포해야 하기때문에 운영환경에서는

좋은 방법이 아니라고 생각되었다.

geoip:
  database:
    path: /application/data/GeoLite2-Country.mmdb

 

 

- GeoIPConfig Class 생성

 

GeoIP DB 를 사용하기 위해 Config Class 를 설정해주도록 하자. 

필자는 운영환경에서만 사용될 예정이기 때문에 Profile 을 추가하여 운영환경에서만 사용해주도록 했다.

@Configuration
@Profile("prod")
public class GeoIpConfig {

    @Value("${geoip.database.path}")
    private String path;

    @Bean
    public DatabaseReader dbReader() throws IOException {
        File file = new File(path);
        return new DatabaseReader.Builder(file).build();
    }
}

 

ClassPathResource classPathResource = new ClassPathResource(GEO_FILE_PATH)

resource 내부에서 사용할 계획이라면 위처럼 File 대신에 사용해주도록 하자.

 

 

- GeoIpFilter Class

 

이제 해당 DB 의 정보를 통해 해외 IP 를 차단시키는 Filter 를 생성해 보도록 하자.

요청 Header 를 통해 Client의 IP 를 추출하여 필터링을 걸어주도록 하자. 

 

유의할점!
일반적으로 Spring Application 으로 직접 들어오는 요청의 경우 Header 의 어떤부분에 
Client IP 가 존재하지 모르기때문에 Client IP 에 관련된 Header 를 for 문돌면서 확인하는 예제들이 존재한다.

다만 필자의 경우 Reverse Proxy 서버를 통해 요청이 들어오기 때문에 무조건 "X-Forwarded-For" 헤더를 통해 Client IP 가 
존재하기 때문에 해당 헤더만 검사해서 필터링을 걸어주었다.

 

@Component
@Slf4j
@Profile("prod")
public class GeoIpFilter implements GlobalFilter, Ordered {

    private final DatabaseReader dbReader;

    public GeoIpFilter(DatabaseReader dbReader) {
        this.dbReader = dbReader;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String clientIp = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For")
                .split(",")[0].trim();

        try {
            InetAddress ipAddress = InetAddress.getByName(clientIp);
            CountryResponse response = dbReader.country(ipAddress);
            String countryName = response.getCountry().getName();

	if (countryName == null || !"South Korea".equals(countryName)) {
                log.info("[GeoIpFilter] Block Country : {}, {}", countryName, clientIp);
                exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);

		return exchange.getResponse().setComplete();
            }
        }catch (UnknownHostException e) {
            log.info("[GeoIpFilter] Block Country Unknown Host: {}", clientIp);
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);

		return exchange.getResponse().setComplete();
        }catch (IOException | GeoIp2Exception e) {
            e.printStackTrace();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

 

해당 Filter 코드를 간단하게 설명해보자면, Config 와 마찬가지로 "prod"(운영) 환경에서만 동작하도록 설정해주었고

X-Forwarded-For Header 를 통해 Client IP 를 가져와 주었다.

 

이후 DB 를 통해 해당 IP 에 대한걸 질의를 하는데 getName() 이 아니더라도, getGeoNameId(), getIsoCode() 를 활용할 수 있다.

필자는 getName() 으로 한국을 제외한 모든국가는 차단하도록 하였고, IP 정보가 없을경우를 대비하여 UnknownHostException 발생시 또한 차단하도록 설정해주었다.

 

Order 의 경우 모든 Filter 중 최우선 적용으로 설정하였다. 해외 IP 일 경우 다음 필터에 갈 필요도 없기 때문이다.

 

 

3. 테스트

https://www.sslproxies.org/

해당 사이트는 해외의 Proxy 서버 리스트의 정보를 갖고있고 운용하고 있으며, 무료 Proxy List 를 위한 사이트이다. 

터미널의 명령어를 통해 테스트를 진행하여 실제로 IP Blocking 이 되는지 확인해 보자. 

참고로 테스트의 경우 Local 에서도 진행 가능한 방법이다.

 

 

- 해외 IP (US)

 

X-Forwarded-For 헤더에 Proxy IP 를 적용하여 요청을 해보면, 403 Forbidden 이 Response 로 오는것을 확인할 수 있다.

 

 

 

- 국내 IP(KR)

국내 IP 의 경우 Route 에 적용된 서버로 요청을 보내거나, 서버가 기동중이지 않다면 Body 를 Response 하도록 했다.

(200Ok 의 경우 임시로 내려준 StatusCode, 운영환경에서는 Body 와 같이 503 Return)

 

국내 IP의 경우 이후 필터를 진행하며 Logging 되고, Process 가 계속진행된다.

 

 

 

운영환경의 테스트 경우, 실제로 Local 과 동일하게 동작하고 있기에 별도의 테스트 사진은 올리지 않았습니다. 

반응형

댓글