서버리스 API 보안 설정

이제 Cognito 사용자 풀과 Identity Pool로 API를 보호했으므로, Lambda 함수에서 인증된 사용자 정보를 사용할 준비가 되었습니다.

지금까지는 사용자 ID를 하드 코딩해왔습니다(사용자 ID 123). 이제 Lambda 함수 이벤트에서 실제 사용자 ID를 가져와야 합니다.

Cognito Identity Id

Lambda 함수의 시그니처를 다시 떠올려 보겠습니다.

export async function main(event: APIGatewayProxyEvent, context: Context) {}

혹은 우리가 사용 중인 리팩토링된 버전:

export const main = Util.handler(async (event) => {});

지금까지는 event 객체를 사용해 경로 파라미터(event.pathParameters)와 요청 본문(event.body)을 가져왔습니다.

이제는 인증된 사용자의 ID를 가져오겠습니다.

event.requestContext.authorizer?.iam.cognitoIdentity.identityId;

이 ID는 Cognito Identity Pool에서 우리 사용자에게 할당된 ID입니다.

또한 지금까지 모든 API가 단일 사용자와 상호작용하도록 하드 코딩되어 있었다는 것을 기억하실 겁니다.

userId: "123", // 작성자의 ID

이제 이를 변경해 보겠습니다.

Change indicator packages/functions/src/create.ts에서 위의 줄을 다음으로 바꿉니다.

userId: event.requestContext.authorizer?.iam.cognitoIdentity.identityId,

Change indicator 다음 파일들에서도 동일하게 변경합니다:

  • packages/functions/src/get.ts
  • packages/functions/src/update.ts
  • packages/functions/src/delete.ts

Change indicator packages/functions/src/list.ts에서는 다음 줄을 찾습니다.

":userId": "123",

Change indicator 그리고 이를 다음으로 바꿉니다.

":userId": event.requestContext.authorizer?.iam.cognitoIdentity.identityId,

위의 userId는 Federated Identity ID(또는 Identity Pool 사용자 ID)라는 점을 기억하세요. 이는 User Pool에서 할당된 사용자 ID가 아닙니다. User Pool 사용자 ID를 사용하고 싶다면, Cognito Identity Id와 User Pool Id 매핑 챕터를 참고하세요.

이 변경 사항을 테스트하기 위해 더 이상 curl 명령어를 사용할 수 없습니다. 요청을 보내기 위해 인증 헤더를 생성해야 합니다. 다음 단계에서 이를 진행해 보겠습니다.

API 테스트하기

인증과 함께 API를 빠르게 테스트해 보겠습니다.

API 엔드포인트에 안전하게 접근하려면 다음 단계를 따라야 합니다.

  1. User Pool에 인증하여 사용자 토큰을 획득합니다.
  2. 사용자 토큰으로 Identity Pool에서 임시 IAM 자격 증명을 얻습니다.
  3. IAM 자격 증명을 사용해 Signature Version 4로 API 요청에 서명합니다.

이 단계를 수동으로 수행하는 것은 조금 까다로울 수 있습니다. 그래서 AWS API Gateway Test CLI라는 간단한 도구를 만들었습니다.

$ npx aws-api-gateway-cli-test \
--user-pool-id='<USER_POOL_ID>' \
--app-client-id='<USER_POOL_CLIENT_ID>' \
--cognito-region='<COGNITO_REGION>' \
--identity-pool-id='<IDENTITY_POOL_ID>' \
--invoke-url='<API_ENDPOINT>' \
--api-gateway-region='<API_REGION>' \
--username='admin@example.com' \
--password='Passw0rd!' \
--path-template='/notes' \
--method='POST' \
--body='{"content":"hello world","attachment":"hello.jpg"}'

위 단계를 완료하려면 상당히 많은 정보를 전달해야 합니다.

  • 위에서 생성한 사용자의 아이디와 비밀번호를 사용합니다.
  • USER_POOL_ID, USER_POOL_CLIENT_ID, COGNITO_REGION, IDENTITY_POOL_ID이전 챕터UserPool, UserPoolClient, Region, IdentityPool로 대체합니다.
  • API_ENDPOINTAPI 생성 시의 Api로 대체합니다.
  • API_REGION은 위에서 사용한 Region과 동일하게 사용합니다. 전체 앱이 동일한 리전에 배포되었기 때문입니다.

이 과정이 복잡해 보일 수 있지만, 실제로는 기본 HTTP 요청을 보내기 전에 보안 헤더를 생성하는 작업일 뿐입니다. React.js 앱에서 연결할 때는 이 과정이 필요하지 않습니다.

명령어가 성공하면 응답은 다음과 비슷하게 나타납니다.

Authenticating with User Pool
Getting temporary credentials
Making API request
{
  status: 200,
  statusText: 'OK',
  data: {
    userId: 'us-east-1:06d418dd-b55b-4f7d-9af4-5d067a69106e',
    noteId: 'b5199840-c0e5-11ec-a5e8-61c040911d73',
    content: 'hello world',
    attachment: 'hello.jpg',
    createdAt: 1650485336004
  }
}

변경 사항 커밋하기

Change indicator 이제 변경 사항을 커밋하고 GitHub에 푸시해 보겠습니다.

$ git add .
$ git commit -m "API 보안 설정"
$ git push

이제 사용자 인증을 처리하고 보안이 적용된 서버리스 API를 만들었습니다. 다음 섹션에서는 서버리스 환경에서 제3자 API를 어떻게 활용할 수 있는지, 그리고 비밀 정보를 어떻게 처리하는지 살펴보겠습니다.