공공 데이터 Open API 삽질기

공공 데이터 Open API 에서 Service Key 인코딩 문제 해결기

공공 데이터 Open API 삽질기
Photo by Silas Köhler / Unsplash

문제 코드

// 	Web 요청
final RestTemplate restTemplate = new RestTemplate();
final String url = "<http://apis.data.go.kr/6410000/busrouteservice/getBusRouteInfoItem>";
final String serviceKey = "QIMPxKy%2B%2FltHKSomethingapikeyxg%3D%3D";
final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
	.queryParam("serviceKey", serviceKey)
	.queryParam("routeId", "234000031");
final String requestUri = builder.toUriString();
""
// 	OpenAPI 호출
ResponseEntity<String> response = restTemplate.getForEntity(requestUri, String.class);
System.out.println(response.getBody());

result

<OpenAPI_ServiceResponse>
	<cmmMsgHeader>
		<errMsg>SERVICE ERROR</errMsg>
		<returnAuthMsg>SERVICE_KEY_IS_NOT_REGISTERED_ERROR</returnAuthMsg>
		<returnReasonCode>30</returnReasonCode>
	</cmmMsgHeader>
</OpenAPI_ServiceResponse>

문제

정상적으로 serviceKey 를 입력했음에도, serviceKey 가 등록되지 않았다는 오류를 반환받음

분석

request URL 변환 과정에 문제가 있다고 가정하고 debug 진행

query 를 간편하게 삽입하기 위해서 UriComponentsBuilder 를 활용하였는데, %unsafe character 로 URI 변환 과정에서 %%25 로 변환된 모습을 볼 수 있었음

URIComponentBuilder

URIComponentBuilder 의 결과를 toUriString() 할 때를 찾아보면 다음을 호출한다.

 this.build().encode().toUriString() 

encode() 과정에서 기존의 encoded 된 key 를 다시 encode 하고 있고 % 를 다시 인코딩하여 문제가 생긴다.

Decoded Key로 변경

그러면 encoding 이전의 key로 변경하고 전달하면 해결되지 않을까

final String url = "<https://apis.data.go.kr/6410000/busrouteservice/getBusRouteInfoItem>";
final String decodedKey = "QIMPxKy+/lt~~~~~==";
final var uri = UriComponentsBuilder.fromUriString(url)
	.queryParam("serviceKey", decodedKey)
	.queryParam("routeId", "234000031")
	.toUriString();
// 	OpenAPI 호출
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);

여전히 같은 오류를 받았으며, QIMPxKy+/lt~~~~~%3D%3D 라고 encode 된 것을 확인했다.

= 의 경우 정상적으로 encode 되어 %3D 로 인코딩되었으나, +/ 의 경우 인코딩되지 않은 것.

https://stackoverflow.com/questions/18138011/url-encoding-using-the-new-spring-uricomponentsbuilder

UriComponentBuilder 를 통해서는 정상적으로 Request 가 불가한 상황이다.

String 합치기

final String uri = url + "?serviceKey=" + encodedKey + "&routeId=" + routeId;

이번에는 String 에서 합치기만 했다. 그러나 여전히 Response 는 같다. (대체 왜?)

Browser 에서는 정상인 링크로 바로 보내기

final String uri = "<https://apis.data.go.kr/6410000/busrouteservice/getBusRouteInfoItem?serviceKey={KEY}&routeId=234000031>";

Browser 에서는 아무런 문제가 없는 링크로도 전송해보았다. 분명 성공해야하는데, 여전히 Response 는 같다.

답답해서 wireshark 로 까봤다.

wireshark 삽질

private relay 가 켜져있는지 꼭 확인하자. 모든 패킷이 private relay 를 통하기에 wireshark 에서 볼 때는 다 암호화가 되어 있다.
이 때문에 HTTP 패킷을 볼 수 가 없었다.

Wireshark 에서 확인해보니 또 다시 encoding 과정을 거친 값으로 실제 리퀘스트가 갔음을 확인했다.

Debugger 에서 URI 값을 확인했을 때는 정상적으로 parameter가 넘어갔다. 그러나, 결국 실제 request 로 날라갈 때는 또 인코딩이 된 상태로 넘어간다. 즉, parameter 상에서 URLEncoded 된 URI 를 넘겨도 또 다시 RestTemplate 의 요청 과정 어디에선가 Encoding 을 한다.

범인찾기 - RestTemplate

RestTemplate 의 과정을 살펴보면 결국 URL 대상으로 요청을 execute 하는 부분이 있다. 이 부분에서 URL 로 다시 바꿔서 요청을 실행하는데 이 부분이 문제다.

불행하게도 위에서와 같은 이유로 decoded 된 key 를 넘기더라도 + 와 같은 문자열이 인코딩되지 않아 정확한 key 를 보낼 수 없다.

따라서, 해당 과정을 피하는 방법을 떠올려야한다.

해결 방안

final URI uri = URI.create(uriString);

딱 이 한 줄만 추가하면 된다...

URIComponentBuilder 부분에서 encoding 하는 문제의 경우에도 해결할 수 있다. 다음과 같이 작성해보자.

final var builder = new DefaultUriBuilderFactory();
builder.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);

final String uriString = builder.builder()
    .scheme("http")
    .host("apis.data.go.kr")
    .path("/6410000/busrouteservice/getBusRouteInfoItem")
    .queryParam("serviceKey", encodedKey)
    .queryParam("routeId", routeId)
    .build()
    .toString();
final URI uri = URI.create(uriString);

사실 아래 글에서 처음에 찾긴 했는데, 어디서 문제를 발생시키는지 당최 파악이 안돼서 열심히 파봤다. 쓸 때마다 늘 염두에 둘 부분인 것 같아 글로 기록해둔다.

[졸업프로젝트]공공데이터 포털 service key is not registered error 해결 방법
공공데이터 포털의 API를 졸업프로젝트를 진행하면서 사용하게 되었는데, 이 과정에서 “service key is not registered error”를 만나게 되었다. 이 오류를 해결하기 위한 과정이 적혀있다.