본문 바로가기

트러블 슈팅

gateway에서 exchange를 사용하여 값을 전달할 때 주의점

문제 정의

gateway에서 exchange통해 값을 넘겨줄 때 문제가 되는 부분이 있었습니다. 이번 프로젝트에서 제가 설계한 gateway에서는 토큰의 유효성을 검증하는 부분이 있었는데요, 예를 들어서 아래처럼 넘겨주게 되면 변환된 exchange 값이 사용되지 못합니다.

private ServerWebExchange validateToken(String token, ServerWebExchange exchange) {
        try {
            SecretKey key = getSigningKey();
            Jws<Claims> claimsJws = Jwts.parser()
                    .verifyWith(key)
                    .build().parseSignedClaims(token);
            Claims claims = claimsJws.getPayload();

            Date expiration = claims.getExpiration();
            if (expiration.before(new Date())) {
                return exchange;
            }

            if ((claims.get("userId") != null) || (claims.get("username") != null) || (claims.get("role") != null)) {
                String userId = claims.get("userId").toString();
                String username = claims.get("username").toString();
                String role = claims.get("role").toString();

                Boolean verifiedUser = authService.verifyUser(VerifyUserDto.builder()
                        .userId(userId)
                        .username(username)
                        .role(role)
                        .build());

                if (verifiedUser) {
                // 이런식으로 사용하게 될 경우 exchange를 제대로 사용할 수 없게 됩니다.
                   exchange.getRequest().mutate()
                            .header("X-User_Id", userId)
                            .header("X-Username", username)
                            .header("X-Role", role)
                            .build();

                    return exchange;
                }
            }
            return exchange;
        } catch (Exception e) {
            log.info(e.getMessage());
            return exchange;
        }
    }

 

왜 그럴까요?

 

해결 과정

문제를 보기 전 이해가고 가야 될 부분이 존재합니다. 바로 ServerHttpRequest에 관한 부분인데요, ServerHttpRequest는 불변 객체입니다. 즉, 한 번 생성된 요청은 변경할 수 없습니다. mutate()를 사용하지 않으면 요청의 헤더나 다른 속성을 직접 수정할 수 없고, 새로 생성된 요청을 사용해야 하는데 exchange.getRequest()로 가져온 객체가 바로 이 ServerHttpRequest객체 입니다. 

더보기

ServerWebExchange와 ServerHttpRequest는 Spring WebFlux에서 HTTP 요청과 응답을 다루기 위한 중요한 클래스입니다. 이 둘은 밀접한 관계가 있으며, 이해하는 것이 중요합니다.

1. ServerWebExchange
ServerWebExchange는 클라이언트의 HTTP 요청과 서버의 HTTP 응답을 캡슐화하는 인터페이스입니다.
이 클래스는 HTTP 요청을 나타내는 ServerHttpRequest와 HTTP 응답을 나타내는 ServerHttpResponse를 포함합니다.
ServerWebExchange는 요청과 응답을 함께 다루기 때문에, 필터나 핸들러에서 요청을 처리하고 응답을 수정하는 데 유용합니다.
2. ServerHttpRequest
ServerHttpRequest는 HTTP 요청을 나타내는 인터페이스입니다. 요청의 메서드(GET, POST 등), URL, 헤더, 바디 등과 같은 정보를 포함합니다.
이 클래스는 요청의 내용을 읽고, 요청에 대한 다양한 메타데이터를 제공하는 역할을 합니다.

이 개념을 들고 문제 해결을 해보자면, exchange.getRequest().mutate()는 요청의 복사본을 생성하고 있습니다. 따라서 mutate() 메서드를 호출한 후 header(...) 메서드로 헤더를 추가하고, build()를 호출해서 새로운 요청을 반환하지만, 이 요청을 exchange에 다시 설정하지 않는다면, 기존 요청은 변경되지 않게 되는거죠!

 

결과

private ServerWebExchange validateToken(String token, ServerWebExchange exchange) {
        try {
            SecretKey key = getSigningKey();
            Jws<Claims> claimsJws = Jwts.parser()
                    .verifyWith(key)
                    .build().parseSignedClaims(token);
            Claims claims = claimsJws.getPayload();

            Date expiration = claims.getExpiration();
            if (expiration.before(new Date())) {
                return exchange;
            }

            if ((claims.get("userId") != null) || (claims.get("username") != null) || (claims.get("role") != null)) {
                String userId = claims.get("userId").toString();
                String username = claims.get("username").toString();
                String role = claims.get("role").toString();

                Boolean verifiedUser = authService.verifyUser(VerifyUserDto.builder()
                        .userId(userId)
                        .username(username)
                        .role(role)
                        .build());

                if (verifiedUser) {
                    ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                            .header("X-User_Id", userId)
                            .header("X-Username", username)
                            .header("X-Role", role)
                            .build();

                    // 교환의 요청을 업데이트
                    exchange = exchange.mutate().request(mutatedRequest).build();
                    return exchange;
                }
            }
            return exchange;
        } catch (Exception e) {
            log.info(e.getMessage());
            return exchange;
        }
    }

 

위 코드와 같이 ServerWebExchange 객체에서 가져온 ServerHttpRequest를, mutate() 메서드를 사용하여 요청을 수정한 후 그 수정된 요청을 exchange에 다시 설정해서 다른 곳에서도 갱신된 정보를 사용할 수 있었습니다.

 

깨달은 내용

이렇게 불변 객체를 활용할 때 주의해야 될 점을 알게 되었습니다. 이 과정을 알아가는데 디버깅이 굉장히 큰 도움이 되었죠. 예전에는 문제를 찾기 위해 무작정 구글링부터 했다면, 이제는 이렇게 저 스스로 문제점을 발견해 나가고 구체적인 해결법을 검색하는 경우도 생기게 되는 것 같습니다.. 좋은 경험이 되었네요!