[SpringBoot] 해외 IP 차단 적용하기

정명진·2023년 9월 22일
0

이번에 출시한 애플리케이션 모아의 접속 로그를 보다가 해외 IP에서 접속을 시도한 내역이 있었다.

은행 앱을 보면 글로벌로 출시된 앱이 아니라면 해외에서 접속 자체를 막아두는 경우가 있다. DDOS 공격을 당했었던 경험 때문이지 않을까 생각이 든다.

그래서 SpringBoot 단에서 ip 차단을 적용해야 겠다고 생각이 들었다.

그 중 나는 GeoIP2를 이용하기로 했다.

https://www.maxmind.com/en/geoip2-databases

해당 페이지에서 가입후 무료버전으로 GeoLite2-Country 라는 db 파일을 받을 수 있다.

다운로드를 했다면 해당 파일을 읽기 위한 라이브러리를 추가해야한다.

implementation("com.maxmind.geoip2:geoip2:2.16.1")

빌드를 하고 나면 Config 파일을 생성해주자.

@Configuration
class GeoIpConfig(
    @Value("\${geo.lite.country}")
    private val path: String,
) {
    @Bean
    fun dataBaseReader(): DatabaseReader {
        val inputStream = ClassPathResource(path).inputStream
        val file: File = File.createTempFile("temp", ".mmdb")

        inputStream.use { input ->
            file.outputStream().use { output ->
                input.copyTo(output)
            }
        }
//        val file = File(path)
        return DatabaseReader.Builder(file).build()
    }
}

그리고 IP 주소를 파싱하는 유틸과 필터를 작성해주자.

class HttpReqResUtils {
    companion object{
        private val IP_HEADERS = arrayOf("X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_X_FORWARDED_FOR",
            "HTTP_X_FORWARDED", "HTTP_X_CLUSTER_CLIENT_IP", "HTTP_CLIENT_IP", "HTTP_FORWARDED_FOR", "HTTP_FORWARDED",
            "HTTP_VIA", "REMOTE_ADDR"
        )

        fun getClientIpAddress(): String {
            if (RequestContextHolder.getRequestAttributes() == null) {
                return "0.0.0.0"
            }
            // else
            val request = (RequestContextHolder.getRequestAttributes() as ServletRequestAttributes).request
            for (header in IP_HEADERS) {
                val ip = request.getHeader(header)
                if (!ip.isNullOrEmpty() && "unknown".equals(ip, true)) {
                    return ip.split(",")[0]
                }
            }
            return request.remoteAddr
        }
    }
}


@Component
@RequiredArgsConstructor
class IpAuthenticationFilter(
    private val databaseReader: DatabaseReader
) : Filter {
    val log = logger()

    override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain?) {
        val clientIpAddress = HttpReqResUtils.getClientIpAddress()
        log.info("clientIpAddress : {}", clientIpAddress)
        val inetAddress = InetAddress.getByName(clientIpAddress)
        var isoCode = ""

        if (clientIpAddress != "127.0.0.1" && clientIpAddress != "0:0:0:0:0:0:0:1") {
            try {
                isoCode = databaseReader.country(inetAddress).country.isoCode
                log.debug("client country : {}", isoCode)
                if (isoCode == null || isoCode != "KR") {
                    log.error("Access Denied - Ip : {}, Country : {}", inetAddress, isoCode)
                    return
                }
            } catch (e: GeoIp2Exception) {
                log.error("geo lite failed...{}", e.message)
                throw CustomException(CustomErrorCode.GEO_LITE_ERROR)
            }
        }
        chain?.doFilter(request, response)
    }
}

이제 실제로 해외에서 접속을 시도해보자

https://tools.pingdom.com

위 사이트에 접속하여 자신의 웹 사이트 주소를 기재후 연결 테스트를 시작하자.

만약 정상적으로 필터가 작동한다면 다음과 같이 로그가 남을것이다.

2023-09-22 16:55:23 [http-nio-8081-exec-4] [ERROR] c.e.m.filter.IpAuthenticationFilter - Access Denied - Ip : /67.217.57.54, Country : US
profile
개발자로 입사했지만 정체성을 잃어가는중... 다시 준비 시작이다..

0개의 댓글