728x90

마이크로서비스 아키텍처에서 서비스들은 서로 독립적으로 존재하지만, 데이터를 요청하거나 이벤트를 발행할 때 요청을 보낸 서비스가 신뢰할 수 있는 내부 서비스인지 확인하는 과정이 필수적입니다. 이것이 바로 서비스 간 인증(S2S 인증)입니다.
API Gateway를 통해 사용자(End-User) 인증이 완료되었더라도, 내부 서비스 간 통신은 별도의 인증 메커니즘을 가져야 합니다.
1. S2S 인증의 필요성
- 보안 경계: 외부 침입자가 내부망에 접근했을 때, 인증되지 않은 서비스가 민감한 데이터를 요청하는 것을 방지합니다.
- 권한 분리: 각 서비스가 수행할 수 있는 역할을 명확히 제한하여 오용을 막습니다.
2. NestJS S2S 인증 구현 방법: Shared Secret (API Key)
가장 간단하고 일반적인 S2S 인증 방법은 Shared Secret (공유 비밀키) 또는 API Key를 사용하는 것입니다. 요청하는 클라이언트가 사전에 정의된 비밀키를 헤더 또는 메타데이터에 담아 전송하고, 수신하는 서버가 이를 검증합니다.
728x90
A. 클라이언트 (Requester) 설정: Secret 전달
ClientProxy를 설정할 때, 요청과 함께 전송할 메타데이터(Metadata)에 비밀키를 포함시킵니다.
// 클라이언트 모듈 (ClientsModule.register)
import { ClientsModule, Transport } from '@nestjs/microservices';
ClientsModule.register([
{
name: 'INTERNAL_USER_SERVICE',
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 3002,
},
},
]),
// ...
클라이언트가 send()나 emit()을 사용할 때, 데이터와 함께 메타데이터를 배열로 전달합니다.
// src/product/product.service.ts
import { ClientProxy } from '@nestjs/microservices';
const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY || 'very-secret-key-1234';
async getInternalUserData(userId: number) {
// 요청 페이로드와 메타데이터를 함께 전달
return this.client.send(
{ cmd: 'find_user_details' },
{
userId,
// 메타데이터: 두 번째 요소로 { headers: { ... } } 객체 전달
headers: {
'internal-api-key': INTERNAL_API_KEY
}
}
).toPromise();
}
B. 서버 (Provider) 설정: Custom Guard로 Secret 검증
마이크로서비스 서버에서는 Custom Guard를 사용하여 요청을 처리하기 전에 전송된 비밀키를 검증합니다.
// src/auth/s2s.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
@Injectable()
export class S2SAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// 1. 컨텍스트에서 데이터와 메타데이터(헤더) 추출
const rpc = context.switchToRpc();
// 데이터는 [data, headers] 형태로 전달되므로, 두 번째 요소를 가져옵니다.
const headers = rpc.getData()[1]?.headers;
const providedKey = headers?.['internal-api-key'];
const expectedKey = process.env.INTERNAL_API_KEY || 'very-secret-key-1234'; // 환경 변수에서 가져옴
// 2. 키 검증
if (providedKey !== expectedKey) {
// 인증 실패 시 RpcException을 발생시켜 클라이언트에게 오류를 전달합니다.
throw new RpcException({
statusCode: 401,
message: 'Unauthorized: Invalid Internal API Key',
});
}
// 3. 인증 성공
return true;
}
}
C. 핸들러에 Guard 적용
메시지 패턴 핸들러에 S2SAuthGuard를 적용하여 인증된 요청만 처리하도록 합니다.
// src/user/user.controller.ts
import { Controller, UseGuards } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
import { S2SAuthGuard } from '../auth/s2s.guard';
@Controller()
// 컨트롤러 전체에 Guard 적용
@UseGuards(S2SAuthGuard)
export class UserController {
@MessagePattern({ cmd: 'find_user_details' })
findUserDetails(@Payload() data: any) {
// 인증이 통과된 요청만 이 로직을 실행
console.log(`인증된 서비스로부터 사용자 요청 수신: ${data.userId}`);
// ... 로직
return { id: data.userId, details: '...' };
}
}
728x90
3. JWT Propagation (고급)
더 복잡한 시나리오에서는 최초의 사용자(End-User) 인증 정보를 내부 서비스 간에 전달해야 할 수 있습니다 (JWT 전파).
- API Gateway가 클라이언트로부터 JWT를 수신하고 검증합니다.
- API Gateway는 이 사용자 JWT를 내부 S2S 인증키와 함께 내부 마이크로서비스로 전달합니다.
- 마이크로서비스는 S2S 키로 요청의 유효성을 먼저 확인한 후, 전달된 사용자 JWT를 사용하여 해당 요청을 보낸 사용자의 권한을 확인합니다.
728x90
'Nest.js를 배워보자 > 12. Microservices — NestJS로 MSA 만들기' 카테고리의 다른 글
| API Gateway 패턴 (0) | 2025.12.03 |
|---|---|
| RabbitMQ, Kafka 연동 (이벤트 기반 통신) (0) | 2025.12.03 |
| TCP 기반 마이크로서비스 (Microservice) (0) | 2025.12.03 |