만약에 사용자 관련 정보를 body나 path 에 담아서 보내도록 설계해 놓는다면, 악의적인 사용자가 수정 후 다른 유저의 상태로 접근이 가능하게 된다. 이를 방지하기 위해 JWT 토큰을 통해 암호화된 정보를 준 뒤 단일 진입점(Spring Cloud Gateway)에서 JWT 토큰을 복호화해서 정보를 헤더에 담아서 마이크로 서비스에게 전달하는 방식으로 구현하고자 한다.




그래서 작업을 시작하기 전 백엔드 개발자들에게 header에서 userId, nickname을 가져오도록 해야한다.. (우리는 처음에 그렇게 안해서 다시 하고 있다..)
프론트엔드는 유저 관련 정보를 보내지 않는다. JWT 토큰만 가지고 있다가 header에 담아서 보내게 된다.
// 프론트엔드 코드
fetch('/api/v1/ranking/submit-score', {
method: 'POST',
headers: {
'Authorization': 'Bearer eyJ...' // JWT만!
},
body: JSON.stringify({
score: 9999 // 비즈니스 데이터만
})
})
프론트 요청에서 헤더에 있는 JWT 토큰을 복호화 후 header에 userId, nickname을 더해서 마이크로 서비스로 전달한다.
// Spring Cloud Gateway Filter
public class JwtAuthenticationFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. Authorization 헤더에서 JWT 추출
String token = extractToken(exchange.getRequest());
// 2. JWT 검증 및 파싱
Claims claims = jwtTokenProvider.parseClaims(token);
String userId = claims.get("user_id", String.class);
String nickname = claims.get("nickname", String.class);
// 3. 헤더에 추가해서 백엔드로 전달
ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
.header("X-User-Id", userId)
.header("X-User-Nickname", nickname)
.build();
return chain.filter(exchange.mutate().request(modifiedRequest).build());
}
}
이제 Gateway가 검증한 정보이므로 신뢰하고 사용자 정보를 추출해서 사용할 수 있다.
// Ranking Server Controller
@PostMapping("/submit-score")
public ResponseEntity<Void> submitScore(
@RequestHeader("X-User-Id") Long userId, // 헤더에서 추출
@RequestHeader("X-User-Nickname") String nickname,
@RequestBody ScoreRequest request) { // body는 비즈니스 데이터만
// userId, nickname은 Gateway가 검증한 것이므로 신뢰 가능
rankingService.submitScore(userId, nickname, request.getScore());
return ResponseEntity.ok().build();
}
처음에는 사용자 정보를 body나 path에 담아 보내는 구조로 설계했지만,
이 경우 악의적인 사용자가 요청 값을 조작해 다른 유저의 권한으로 접근할 수 있는 보안 문제가 발생할 수 있었다.
이를 해결하기 위해, JWT 기반 인증 구조로 전체 시스템을 개선했다.
이제 프론트엔드는 사용자 정보를 직접 전달하지 않고, 오직 JWT 토큰만 헤더에 포함하여 요청한다.
Spring Cloud Gateway가 단일 진입점 역할을 하며, 이 토큰을 복호화해 userId, nickname 등의 정보를 추출하고
이를 각 마이크로서비스로 신뢰할 수 있는 헤더 형태로 전달한다.
백엔드 서비스는 더 이상 JWT를 직접 검증하지 않아도 되며,
Gateway가 전달한 사용자 정보를 그대로 신뢰 가능한 인증 정보로 사용할 수 있다.
결과적으로,
라는 세 가지 효과를 얻을 수 있었다.
현재는 모든 백엔드 서비스가 헤더에서 X-User-Id, X-User-Nickname을 읽어 사용하는 방식으로 통합되어,
보안성과 유지보수성이 크게 개선된 구조로 운영되고 있다.
JWT 토큰을 통해 암호화된 정보를 준 뒤
JWT에서 사용자 관련 정보(payload)는 암호화되지 않습니다.
이 경우 악의적인 사용자가 요청 값을 조작해 다른 유저의 권한으로 접근할 수 있는 보안 문제가 발생할 수 있었다.
JWT도 가능하지만 위변조를 판별하기 때문에 검증에서 실패하는 것입니다.
Gateway가 전달한 사용자 정보를 그대로 신뢰 가능한 인증 정보로 사용할 수 있다.
이것에 대해 좀더 비판적인 사고가 담겼으면 좋겠습니다. 맨 앞단에서 인증이 되면 그 뒤의 서버는 모든 트래픽을 무작정 믿어도 될까..?