문제 정의
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에 다시 설정해서 다른 곳에서도 갱신된 정보를 사용할 수 있었습니다.
깨달은 내용
이렇게 불변 객체를 활용할 때 주의해야 될 점을 알게 되었습니다. 이 과정을 알아가는데 디버깅이 굉장히 큰 도움이 되었죠. 예전에는 문제를 찾기 위해 무작정 구글링부터 했다면, 이제는 이렇게 저 스스로 문제점을 발견해 나가고 구체적인 해결법을 검색하는 경우도 생기게 되는 것 같습니다.. 좋은 경험이 되었네요!
'트러블 슈팅' 카테고리의 다른 글
spring에서 cache 활용 시 key 값 설정 주의점 (1) | 2024.12.18 |
---|---|
gateway에서의 FeignClient 사용 (0) | 2024.12.18 |
gateway와 각 서버별 circuitbreaker 적용의 이해 (1) | 2024.12.18 |
@ModelAttribute 매핑 시 null값에 대한 이해 (0) | 2024.12.18 |
MSA 구성 시 데이터 정합성의 문제 (0) | 2024.12.18 |