Spring Cloud Gateway 인코딩 이슈
http://ybs.com/filtering?sort[]=id,desc&keyword=%EC%96%91%EB%B4%89%EC%88%98
위와 같은 URL 로 요청이 왔을 때, Spring Cloud Gateway 에서 뒷단 서버로 보내기 전 인코딩 여부 검사를 한다. 그런데 '[' 문자가 Invalid character 로 exception 이 발생하면서 인코딩이 안됐다고 판단해서, 재인코딩을 하게 되고 최종적으로는 keyword 값이 두번 인코딩한 결과가 나온다(정작 중요한 '[', ']' 문자는 인코딩안되고 이미 된것만 또 인코딩한 꼴).
'[' 문자가 왜 Invalid character 로 exception 이 발생했나를 알아보자. Spring Cloud Gateway 는 허용하는 QUERY_PARAM 을 따로 정의해 놨는데 RFC3986 스펙에 맞게 정의해놨다.
query = *( pchar / "/" / "?" )
—————
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
pct-encoded = "%" HEXDIG HEXDIG
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
여기에 '[' 와 ']' 는 없는것을 알 수 있다. 문제는 Spring Cloud Gateway 에서 인코딩 검사를 수행하는곳이 RouteToRequestUrlFilter 인데 고정으로 박힌 필터여서 제거를 못한다. 위 URL 요청을 전달했을 때 이전 프록시 서버에서는 문제없었는데 Spring Cloud Gateway 로 전환하면서 문제가 발생했고, 사용자가 '[', ']' 문자를 인코딩해서 보내줘야만 했다.
Spring Cloud Gateway 에서 인코딩을 확인하는 코드를 한번 자세히 살펴보자(ServerWebExchangeUtils 에서 제공하는 메서드).
public static boolean containsEncodedParts(URI uri) {
boolean encoded = uri.getRawQuery() != null && uri.getRawQuery().contains("%") || uri.getRawPath() != null && uri.getRawPath().contains("%");
if (encoded) {
try {
UriComponentsBuilder.fromUri(uri).build(true);
return true;
} catch (IllegalArgumentException var3) {
if (log.isTraceEnabled()) {
log.trace("Error in containsEncodedParts", var3);
}
return false;
}
} else {
return encoded;
}
}
먼저 query string 이나 path 에 % 문자가 있는지 간단히 1차 체크 하고 UriComponentsBuilder.fromUri(uri).build(true); 이 안에서 다시한번 verify check를 한다. 정확히는 build 에서 HierarchicalUriComponents 객체가 만들어질때 마지막 단계에서 검사한다.
private void verify() {
verifyUriComponent(this.getScheme(), HierarchicalUriComponents.Type.SCHEME);
verifyUriComponent(this.userInfo, HierarchicalUriComponents.Type.USER_INFO);
verifyUriComponent(this.host, this.getHostType());
this.path.verify();
this.queryParams.forEach((key, values) -> {
verifyUriComponent(key, HierarchicalUriComponents.Type.QUERY_PARAM);
Iterator var2 = values.iterator();
while(var2.hasNext()) {
String value = (String)var2.next();
verifyUriComponent(value, HierarchicalUriComponents.Type.QUERY_PARAM);
}
});
verifyUriComponent(this.getFragment(), HierarchicalUriComponents.Type.FRAGMENT);
}
build(true) 에서 인자로 넘기는 boolean 값은 인코딩이 되어있는지 아닌지를 결정하는 값이다. true 이므로 인코딩이 되어있다고 설정하고 작업을 진행한다.