☁️ 뭉게뭉게 클라우드

[AWS | Cloud] API Gateway , Lambda-JWT 인증 적용

우주수첩 2025. 7. 3. 15:14
728x90

2차 프로젝트에서 MSA기반으로 마이그레이션 하면서 JWT 인증 기능을 어떻게 설계할지 골이 조금 아팠는데 말입니다.

하는 김에 API GW랑 람다를 써서 서버리스 기반효율이 좋은 서비스를 구축해보자는 생각이 들었답니다.

 

이 둘을 굳이 사용하겠다고 선정한 이유는 뭐냐면 우선 사용한 만큼 금액이 나오고, 프리티어기간이 남아있어서

적용 했을 때 비용적으로 상당히 효율적인 접근이라고 생각했다지요.

  • Lambda + APIGW 프리이터 제공량 : 매 달 100만건의 요청 및 호출
  • 테스트 외에 요청하는 경우 굉장히 드물었고, JWT를 위해 ECS 서비스를 하나 더 생성하는거는 좀 비효율적이라고 생각했었음

 

또, 1차 프로젝트 결과물을 가지고 취약점 분석을 진행했을 때, ALB 무단 접근 같은 상황이 발생할 수 있다는 것을 인지했어여.

그래서 ALB로 직접 도달이 아니라 그 앞단에서 IP를 확인 가능하게 설계하자고 의견을 제시했고, 그게 API GW가 되는게 좋겠다고 생각했습니다. 

 

API GW랑 람다에서 CORS 설정이 되더라구요?

아직 CI/CD 파이프라인이 구축되어있지 않은 상태에서 스프링 파일에서 CORS Config 처리하려면 여간 귀찮은 게 아니란 말이에요.

근데 이제, 여기서 처리하면 배포를 새로 하지 않아도 되는 편리함.

이 얼마나 좋은가.

 

하여 선정하게 되었습니다. 

 

그래서. 여튼. 어쨌든 간에. 

APIGW 적용 해보겠음. 

 


 

1. APIGW 생성

  • 필자는 HTTP API 로 진행했슴다.
  • 스킵하겠음. 생각보다 EZ하기에.

 

2. 경로 설정 == Routes

  • 서버 엔드포인트에 맞는 경로 지정
  • HTTP 요청에 맞춰서(GET,POST...) 진행
  • 귀찮으면 모든 요청을 허용하는 ANY 추천 
  • OPTIONS : CORS 처리할 때 사용 → 모든 경로에 설정 필요
  • {proxy+} : proxy에 어떤 값이 오던 해당 경로로 매핑한다
    • 필자의 팀의 경우 엔드포인트가 user/login | user/signup 이런 식으로 되어있단말이죵
    • 그래서 user/{proxy} == proxy에 어떤 엔드포인트가 오던 해당 경로로 매핑한다는 의미랍니당

⇒ 여러분들 API 명세서에 맞게 작성해주시면 댄다지요.

 

 


3. Authorization → JWT 인증

 

3_1. JWT 복호화 : 사용자 정보 추출 Lambda 함수 생성

import jwt from 'jsonwebtoken';

const SECRET_KEY = Buffer.from(
  'jwt 시크릿키',
  'base64'
);

export const handler = async (event, context) => {
  const rawToken = event.headers?.authorization || event.headers?.Authorization;
  const authorizationToken = rawToken?.split(' ')[1];

  if (!authorizationToken) {
    return { isAuthorized: false };
  }

  try {
    const token = jwt.verify(authorizationToken, SECRET_KEY, { algorithms: ['HS256'] });
    const userId = token.userId;
    console.log('JWT 검증 성공:', userId);

    return {
      isAuthorized: true,
      context: {
        userId: userId.toString(), // context 값은 문자열이어야 합니다
      }
    };
  } catch (err) {
    console.error('JWT 검증 실패:', err);
    return { isAuthorized: false };
  }
};

 

흐름

1. 로그인 시 session storage에 JWT 토큰 저장

2. 프론트에서 백으로 요청 시 authorization 헤더의 토큰 값 추출

const rawToken = event.headers?.authorization || event.headers?.Authorization;
const authorizationToken = rawToken?.split(' ')[1];

3. 토큰 복호화

const token = jwt.verify(authorizationToken, SECRET_KEY, { algorithms: ['HS256'] });
const userId = token.userId;
console.log('JWT 검증 성공:', userId); -> Cloudwatch 로그 기록
  • algorithms: ['HS256'] : JWT 토큰 생성할때 지정한 알고리즘에 맞게 설정 필요

4. 반환

return {
      isAuthorized: true,
      context: {
        userId: userId.toString(), // context 값은 문자열이어야 합니다
      }
    };
  • isAuthorized: true : lambda v2인가에서부터는 얘가 필수로 들어가야 동작이 됩니다.(알고 싶지 않았어...)
  • APIGW 로 전달해야 하는 값을 context에 넣어서 전달

5. 좌측에 Deploy 눌러야해욥

 

 

3_2. 권한 부여자 관리

 

  • Lambda로 권한 부여자 생성해주시면 됩니답

 

3_3. 인증 연결

JWT 인증이 필요한 엔드포인트에 함수 연결

== 필자의 팀은 account 관련 작업 진행 할 때마다 사용자 인증을 받아서, 계좌 관련 엔드포인트 두 곳에 인증 절차를 박아버렸답니당

 

 

3_4.Integrations - 목적지 설정

1. 통합 생성

== 최종 목적지 생성

== 서버가 돌아가고 있는 곳

ex1) ALB - ECS cluster - Service(user service) → ALB arn이 최종 목적지

ex2) 그냥 EC2 위에서 돌아가고 있으면 EC2 private ip가 최종 목적지

 

2. 통합 연결

필자의 경우

  • Account관련 엔드포인트는 모두 Account 서비스가 돌아가고 있는 ECS 와 연결되어있는 ALB로 냅다 보내버렸습니다.
  • User(login/logout/siginup..)와 관련된 엔드포인트는 모두 User 서비스가 돌아가고있는 ECS 와 연관되어있는 ALB로 냅다 보내버렸습니다

+a) 매개변수 매핑

편의성을 위한 작업인데용

하셔두 대고 안하셔도 대구

저는 백엔드 다시 배포하는게 너무 싫어서 했슴당

 

 

  • 앞에서 Lambda 함수에서 context에 userID 넣은거 기억나시나요
  • 값 : API GW가 지금 가지고 있는 값
  • 수정할 매개변수 : 백엔드에서 헤더 조회시 볼 수 있는 값
    람다에서 JWT 복호화 이후 context에 넣은 userId값을 auth-userId로 HTTP헤더에 넣어서 백으로 요청한다
    ⇒ 백에서는 header값을 받아와 auth-userId라는 변수로 접근 가능하도록

 


 

4. CORS 처리

 

4_1. CORS fonfig 관련 lambda함수 생성

export const handler = async (event) => {
  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin": "*", // 또는 정확한 도메인
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization"
    },
    body: JSON.stringify({ message: "Preflight OK" }),
  };
};

 

  • 원래 * 여기에 localhost:8080이나 도메인 이름 넣어야 보안 측면으로 올바른 설정 방법임다!

 

4_2. 통합 생성

  • 아까 생성한 CORS 관련 람다 함수를 사용해서 통합을 하나 생성해줍니다

 

 

4_3. 통합연결

 

 

  • 모든 OPTION에 해당 통합을 연결해주세요
  • → 이게 APIGW 에서 매핑 직전에 OPtion을 먼저 들렀다가 나가는 형식인 것 같더라구여

 


 

5. 흐름 확인

 

  • 파라미터에 요청이 들어온 헤더(requestHeader)로부터 auth-userId (매개변수 매핑한 값) 을 가져와서 식별할때 사용하는 흐름 입니당

 

 

 

 

 

 

 

끗!

 

 

728x90