본문 바로가기
Spring

Spring Cloud Gateway 인코딩 이슈

by ybs 2021. 6. 18.
반응형

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 이므로 인코딩이 되어있다고 설정하고 작업을 진행한다.

반응형