- Published on
IP 기반 위치 조회 구현 개인적인 정리
- Authors
- Name
- 김민석
Introduction
- 개요
- 상황
- 문제 1) X-Forwarded-For 헤더가 null로 출력됨
- 해결 방법 1) 공인 IP 조회 기능 적용
- 문제 2) API 호출 중 NumberFormatException 발생
- 해결 방법 2) 데이터 변환 로직 개선
- Argument Resolver 적용하여 자동 주입
- 결론 & 얻은 것
개요
IP 기반으로 사용자 위치를 조회하는 기능을 구현하는 과정에서 여러 문제가 발생했다.
특히, X-Forwarded-For 헤더
문제, 공인 IP 조회 방식, Argument Resolver
적용 과정에서의 예외 발생 등의 이슈를 경험했다.
이러한 문제들을 해결한 과정을 상세히 다루고자 한다.
상황
클라이언트의 IP를 기반으로 위치 정보를 조회하는 기능이 필요했다.
외부 API를 활용하여 IP 정보를 가져오고, 해당 IP의 위치 정보를 조회하는 방식을 고려했다.
주요 요구사항:
클라이언트의 IP를 추출하여 위치 정보를 가져와야 한다.
API를 활용하여 공인 IP를 조회하고, 해당 IP의 위치 정보를 반환해야 한다.
로컬 환경에서도 운영 환경에서 동일한 방식으로 동작해야 한다.
컨트롤러에서 직접 서비스를 호출하지 않고, Argument Resolver를 활용하여 위치 정보를 자동으로 주입해야 한다.
문제 1) X-Forwarded-For 헤더가 null로 출력됨
발생한 문제

- API를 통해 클라이언트의 IP를 조회하려 했으나,
X-Forwarded-For
값이 항상 null로 출력되는 문제가 발생했다. - 이미지와 같이,
curl
요청을 보냈을 때 "원격 서버에 연결할 수 없습니다."라는 오류가 발생했다.
원인 분석
X-Forwarded-For
헤더는 프록시 서버(Nginx, AWS Load Balancer 등)를 거칠 때만 추가된다는 것이였다.. (아는 만큼 보인다고, 난 당연히 될거라고 생각 했다..)- 로컬 환경에서 직접 실행하는 경우, 프록시를 거치지 않기 때문에 기본적으로 포함되지 않았다.
request.getRemoteAddr()
를 사용하면 내부 네트워크 IP(예:127.0.0.1
)가 반환됨.
X-Forwarded-For
헤더를 사용하려 했으나, 로컬 환경에서는 항상 null이었다..request.getRemoteAddr()
를 사용했지만, 운영 환경에서는 프록시를 거쳐야 하므로 일관성이 없었다고 판단.
해결 방법 1) 공인 IP 조회 기능 적용
- 로컬 환경에서도 실제 공인 IP를 조회할 수 있도록, 외부 API(
https://checkip.amazonaws.com
)를 활용하는 방식을 적용했다.
적용한 방법
PublicIpProvider
클래스를 생성하여 공인 IP를 가져오는 기능을 별도로 분리 했다.RestTemplate
을 활용하여 외부 API를 호출하고 공인 IP를 반환 했다.GeoLocationService
에서 로컬 환경에서는 공인 IP를 가져오도록 처리하도록 구현
적용 코드
@Component
@RequiredArgsConstructor
public class PublicIpProvider {
private static final String PUBLIC_IP_API = "https://checkip.amazonaws.com";
private final RestTemplate restTemplate;
public String getPublicIp() {
try {
return restTemplate.getForObject(PUBLIC_IP_API, String.class).trim();
} catch (Exception e) {
throw new RuntimeException("공인 IP 조회 실패", e);
}
}
}
적용 후 결과
- 로컬 환경에서도 정상적으로 공인 IP를 가져올 수 있게 되었다.
- 운영 환경에서는 기존
X-Forwarded-For
헤더를 활용하는 방식 유지.
문제 2) API 호출 중 NumberFormatException 발생
발생한 문제

- 공인 IP 조회 기능을 적용한 후, 외부 API에서 반환된 데이터를 처리하는 과정에서 NumberFormatException이 발생했다.
- 이미지와 같이, "empty String"을
Double.parseDouble()
로 변환하려다 예외가 발생했다.
원인 분석
- API 응답에서
loc
필드(위도, 경도 정보)가 비어 있는 경우가 존재함. "loc": "37.386,-122.084"
형식의 문자열을split(",")
하여 파싱해야 하는데, 값이 비어 있으면 NumberFormatException이 발생 했다.- 단순히
Double.parseDouble()
을 사용했으나, 값이 비어 있을 경우를 처리하지 않아 예외가 발생 try-catch
블록 없이split(",")
을 수행했기 때문에, 비어 있는 값이 그대로 전달되면서 오류가 발생
해결 방법 2) 데이터 변환 로직 개선
API 응답 데이터에서 위도와 경도 정보를 추출하는 extractLatLong()
메서드를 개선했다.
private double[] extractLatLong(JsonNode root) {
double latitude = 0.0;
double longitude = 0.0;
String loc = root.path("loc").asText(); // "37.386,-122.084"
if (loc.contains(",")) {
String[] latLong = loc.split(",");
try {
latitude = Double.parseDouble(latLong[0]);
longitude = Double.parseDouble(latLong[1]);
} catch (NumberFormatException e) {
}
}
return new double[]{latitude, longitude};
}
적용한 방법
loc
필드가 비어 있거나"bogon": true
인 경우 기본값(0.0, 0.0)을 반환한다.split(",")
을 수행하기 전에 값이 유효한지 확인한다.- 예외 발생 시 안전하게 기본값을 반환하도록
try-catch
블록 추가
Argument Resolver 적용하여 자동 주입
IP는 컨트롤러 앞단에서 처리하는 게 좋다?
- IP 추출은 컨트롤러 앞단에서 처리하는 게 좋다.
- 이유는 IP는 모든 요청에서 동일한 방식으로 처리되므로, 요청이 들어오는 순간 한 번만 추출하면 되기 때문이다.
- 즉, 모든 컨트롤러에서 중복으로 IP를 추출하는 것보다, 한 곳에서 공통으로 처리하는 것이 좋다고 판단했다.
결론 & 얻은 것
문제 해결.
X-Forwarded-For
가null
이 되는 문제를 공인 IP 조회 기능으로 해결 했다.- Argument Resolver를 적용하여 한 곳에서 공통으로 처리 했다.
- 로컬과 운영 환경에서 동일한 방식으로 동작하도록 개선했다.
얻은 것.
- IP 기반 위치 조회 시스템을 구축할 때 고려해야 할 사항을 학습했다.
- 로컬과 운영 환경의 차이를 고려한 해결 방법 적용 경험했다.
- Argument Resolver를 활용한 자동 주입 방식의 이점 경험했다.