Spring cloud gateway에서 허가되지 않은 url character 사용하기

2024. 2. 7. 17:18Development

레거시 시스템에 Spring cloud gateway를 도입하면서 하나의 장벽에 부딪히게 되었습니다.

오랫동안 유지되어온 시스템에서 client들에게 완벽한 하위호환을 보장해주어야하는 상황에서 일부 client들이 url에 포함해서는 안되는 특수문자들을 인코딩을 하지 않고, 날 것으로 그대로 보내오고 있었습니다.

 

http://localhost:8080/test/case/key/var1|var2

[2dd57409, L:/127.0.0.1:8080 - R:/127.0.0.1:59831] Decoding failed: REQUEST(decodeResult: failure(java.net.URISyntaxException: Illegal character in path at index 40: http://localhost:8080/test/case/key/var1|var2), version: HTTP/1.1)

 

사용되어지는 특수문자들도 심지어 여러가지..

|, ^, {, } 등등.. 모두 url 인코딩이 필요한 character들인데, 인코딩 없이 request가 들어오고 있는 상황에서 문제는 기본적으로 Spring cloud gateway 내부에서 requet url을 이용하여 JAVA URI object를 생성하면서 URISyntaxException 을 발생 시킨다는 것 입니다.

 

실제 저희가 작성 중인 besinsess 로직으로 오기도 전에 framework 에서 자체적으로 400 을 리턴해버리고 있기 때문에 해당 framework 코드 동작 전에 무언가 조치가 필요한 상황이었습니다.

 

다행히도 netty 레벨에서 동작에 network state에 전환되는 중간에 custom 한 code를 개입할 수 있고, 이때 request 에 손을 대는 것이 가능했고 아래와 같이 이슈를 해결하였습니다.

@Slf4j
@Component
public class NettyWebServerCustomizer
        implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
    @Override
    public void customize(NettyReactiveWebServerFactory factory) {
        factory.addServerCustomizers(serverCustomizers -> serverCustomizers
                .childObserve(new CustomEncoderObserver())
        );
    }
}

 

public class CustomEncoderObserver implements ConnectionObserver {
    @Override
    public void onStateChange(@NonNull Connection connection, @NonNull State newState) {
        if (newState == ConnectionObserver.State.CONNECTED) {
            connection.channel().pipeline()
                    .addAfter(
                            NettyPipeline.HttpCodec, "", new ChannelInboundHandlerAdapter() {

                                @Override
                                public void channelRead(
                                        @NonNull ChannelHandlerContext channelHandlerContext,
                                        @NonNull Object message) {
                                    if (message instanceof HttpRequest) {
                                        HttpRequest request = (HttpRequest) message;
                                        if (/* request.uri()를 받아와서 확인하고 싶은 포인트를 검증 */) {
                                            request.setUri(/* uri를 변경한 것을 다시 SET */);
                                        }
                                    }
                                    channelHandlerContext.fireChannelRead(message);
                                }

                                @Override
                                public boolean isSharable() {
                                    return true;
                                }
                            });
        }
    }
}

 

Connection을 모니터링 한 후, connected 는 모든 연결에 최초 1회만 일어나기 때문에 이때 개입하여 특정 인코딩이 되지 않은 특수문자들에 대해서 수동으로 인코딩된 값으로 replace 하는 형태로 문제를 해결할 수 있었습니다.