IAM 인증으로 API Gateway 연결하기

AWS IAM으로 보호된 API Gateway 엔드포인트에 연결하는 것은 까다로울 수 있습니다. Signature Version 4를 사용해 요청에 서명해야 합니다. 이를 위해 다음을 사용할 수 있습니다:

생성된 SDK는 변경이 있을 때마다 다시 생성해야 하기 때문에 사용하기 어려울 수 있습니다. 그리고 AWS Amplify 설정 챕터에서 AWS Amplify를 사용해 앱을 설정하는 방법을 다룹니다.

하지만 AWS JS SDK를 사용해 API Gateway에 간단히 연결하려는 경우, 독립적으로 사용할 수 있는 sigV4Client.js를 만들었습니다. 이는 생성된 SDK에 미리 패키징된 클라이언트를 기반으로 합니다.

이번 챕터에서는 sigV4Client.js를 사용하는 방법을 살펴보겠습니다. 기본적인 흐름은 다음과 같습니다:

  1. Cognito 사용자 풀로 사용자를 인증하고 사용자 토큰을 획득합니다.
  2. 사용자 토큰으로 Identity Pool에서 임시 IAM 자격 증명을 가져옵니다.
  3. IAM 자격 증명을 사용해 Signature Version 4로 API 요청에 서명합니다.

Cognito User Pool로 사용자 인증하기

다음 방법으로 Cognito User Pool에 사용자를 인증할 수 있습니다.

function login(username, password) {
  const userPool = new CognitoUserPool({
    UserPoolId: USER_POOL_ID,
    ClientId: APP_CLIENT_ID,
  });
  const user = new CognitoUser({ Username: username, Pool: userPool });
  const authenticationData = { Username: username, Password: password };
  const authenticationDetails = new AuthenticationDetails(authenticationData);

  return new Promise((resolve, reject) =>
    user.authenticateUser(authenticationDetails, {
      onSuccess: (result) => resolve(),
      onFailure: (err) => reject(err),
    })
  );
}

USER_POOL_IDAPP_CLIENT_ID를 사용해야 합니다. 그리고 사용자의 Cognito usernamepassword를 제공하면 다음과 같이 호출하여 사용자를 로그인할 수 있습니다.

await login("my_username", "my_password");

임시 IAM 자격 증명 생성하기

사용자가 인증되면 임시 자격 증명을 생성할 수 있습니다. 이를 위해 먼저 다음 코드를 사용하여 JWT 사용자 토큰을 가져와야 합니다:

function getUserToken(currentUser) {
  return new Promise((resolve, reject) => {
    currentUser.getSession(function (err, session) {
      if (err) {
        reject(err);
        return;
      }
      resolve(session.getIdToken().getJwtToken());
    });
  });
}

현재 로그인한 사용자는 다음 코드로 가져올 수 있습니다:

function getCurrentUser() {
  const userPool = new CognitoUserPool({
    UserPoolId: config.cognito.USER_POOL_ID,
    ClientId: config.cognito.APP_CLIENT_ID,
  });
  return userPool.getCurrentUser();
}

그리고 JWT 토큰을 사용하여 임시 IAM 자격 증명을 생성할 수 있습니다:

function getAwsCredentials(userToken) {
  const authenticator = `cognito-idp.${config.cognito.REGION}.amazonaws.com/${config.cognito.USER_POOL_ID}`;

  AWS.config.update({ region: config.cognito.REGION });

  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: config.cognito.IDENTITY_POOL_ID,
    Logins: {
      [authenticator]: userToken,
    },
  });

  return AWS.config.credentials.getPromise();
}

API Gateway 요청에 Signature Version 4로 서명하기

sigV4Client.js를 사용하려면 crypto-js가 설치되어 있어야 합니다.

프로젝트 루트에서 다음 명령어를 실행하여 설치하세요.

$ npm install crypto-js --save

그리고 sigV4Client.js를 사용하려면 해당 파일을 프로젝트로 복사하세요.

sigV4Client.js

이 파일은 처음 보면 조금 복잡해 보일 수 있지만, 임시 자격 증명과 요청 파라미터를 사용해 필요한 서명된 헤더를 생성하는 역할을 합니다. 새로운 sigV4Client를 생성하려면 다음과 같은 정보를 전달해야 합니다.

// 의사 코드

sigV4Client.newClient({
  // AWS 임시 액세스 키
  accessKey,
  // AWS 임시 시크릿 키
  secretKey,
  // AWS 임시 세션 토큰
  sessionToken,
  // API Gateway 리전
  region,
  // API Gateway URL
  endpoint,
});

요청에 서명하려면 signRequest 메서드를 사용하고 다음 정보를 전달해야 합니다.

// 의사 코드

const signedRequest = client.signRequest({
  // HTTP 메서드
  method,
  // 요청 경로
  path,
  // 요청 헤더
  headers,
  // 요청 쿼리 파라미터
  queryParams,
  // 요청 본문
  body,
});

signedRequest.headers를 통해 요청에 필요한 서명된 헤더를 얻을 수 있습니다.

sigV4Client로 API Gateway 호출하기

이제 모든 것을 종합해 보겠습니다. 다음은 API Gateway 엔드포인트를 호출하기 위한 간단한 헬퍼 함수입니다.

function invokeApig({
  path,
  method = "GET",
  headers = {},
  queryParams = {},
  body
}) {

  const currentUser = getCurrentUser();

  const userToken = await getUserToken(currentUser);

  await getAwsCredentials(userToken);

  const signedRequest = sigV4Client
    .newClient({
      accessKey: AWS.config.credentials.accessKeyId,
      secretKey: AWS.config.credentials.secretAccessKey,
      sessionToken: AWS.config.credentials.sessionToken,
      region: YOUR_API_GATEWAY_REGION,
      endpoint: YOUR_API_GATEWAY_URL
    })
    .signRequest({
      method,
      path,
      headers,
      queryParams,
      body
    });

  body = body ? JSON.stringify(body) : body;
  headers = signedRequest.headers;

  const results = await fetch(signedRequest.url, {
    method,
    headers,
    body
  });

  if (results.status !== 200) {
    throw new Error(await results.text());
  }

  return results.json();
}

YOUR_API_GATEWAY_URLYOUR_API_GATEWAY_REGION을 반드시 교체해야 합니다. 질문이 있으면 댓글로 남겨주세요.