gRPC Interceptor로 인증 미들웨어 구현하기

배경

사내 마이크로서비스 아키텍처에서 서비스 간 통신을 REST에서 gRPC로 전환하는 작업을 진행 중이었다. 기존 REST API에서는 Express 미들웨어로 JWT 인증을 처리했는데, gRPC에서는 어떻게 구현해야 할지 고민이 필요했다.

문제

각 RPC 메서드마다 인증 로직을 넣으면 코드 중복이 심해진다. gRPC에는 Express의 미들웨어 같은 개념이 없어서 처음에는 각 서비스마다 인증 코드를 복사했는데, 유지보수가 어려웠다.

해결: Server Interceptor

gRPC의 Interceptor를 사용하면 모든 RPC 호출 전후에 공통 로직을 실행할 수 있다.

const grpc = require('@grpc/grpc-js');
const jwt = require('jsonwebtoken');

function authInterceptor(call, callback) {
  const metadata = call.metadata;
  const token = metadata.get('authorization')[0];

  if (!token) {
    return callback({
      code: grpc.status.UNAUTHENTICATED,
      message: 'No token provided'
    });
  }

  try {
    const decoded = jwt.verify(token.replace('Bearer ', ''), process.env.JWT_SECRET);
    call.metadata.set('userId', decoded.userId);
    callback(null);
  } catch (err) {
    callback({
      code: grpc.status.UNAUTHENTICATED,
      message: 'Invalid token'
    });
  }
}

const server = new grpc.Server();
server.addService(serviceDefinition, {
  getUser: (call, callback) => {
    const userId = call.metadata.get('userId')[0];
    // 인증된 사용자로 비즈니스 로직 처리
  }
});

클라이언트에서 토큰 전송

const metadata = new grpc.Metadata();
metadata.set('authorization', `Bearer ${token}`);

client.getUser({ id: 123 }, metadata, (err, response) => {
  // handle response
});

결과

  • 인증 로직을 한 곳에서 관리하게 되어 유지보수성 향상
  • 각 RPC 메서드는 비즈니스 로직에만 집중 가능
  • metadata에서 userId를 꺼내 쓰는 패턴으로 통일

다만 공식 문서가 부족해서 실제 구현 예제를 찾는 데 시간이 걸렸다. Interceptor는 아직 실험적인 API라 프로덕션 적용 시 주의가 필요하다.