[MSA] API Gateway 패턴에서의 인증/인가

AMUD·2023년 10월 30일
9

Architecture & MSA

목록 보기
2/2


지난 프로젝트에서도 API Gateway 패턴을 사용하였지만, 시간의 압박 때문에 중간중간 엉성하게 구현한 부분들이 있었다. 이번 프로젝트도 시간은 촉박했지만, 인증/인가 관련 프로세스만큼은 꼭 많은 고민 후에 확정하고 싶었다.

그래서 구체적인 프로세스를 아래와 같이 확정하였는데, 의외로 설계하는 방법이 다들 상이하였다. 고민한 과정을 공유하고 다양한 피드백을 받고자 게시글을 자세히 작성하였다.

🦜 Gateway와 UserService의 분리

완전 처음에는 Gateway에서 모든 인증/인가를 처리하는 것이 효율적이라고 생각했다. 그런데 Gateway도 하나의 서비스이고, 모든 요청이 Gateway를 거치고 가기 때문에 너무 큰 부담이 예상되었다.

특히 UserService는 서비스들 중에 트래픽이 크기 때문에 확장성을 고려해야 하는데, 단일 진입구인 Gateway와 분리하는 것은 꼭 필요해보였다. 물론 Gateway의 단일 장애 지점(SPOF)의 단점을 보완하기 위해 Gateway 자체에 대한 로드밸런싱이 있는 등의 여러 장치가 있지만, 트래픽이 큰 UserService와 분리하여 장애를 격리하는 것도 필요해보인다.

더 나아가 Gateway는 외부에 공개되기 때문에 보안 측면에서도 분리하는 것이 더 적합해보였다.

이후의 고민들이 대부분 GatewayService, UserService 중에서 어떤 서비스에서 처리해야 더 합리적인지 고민하는 내용이다.

🍒 회원가입

/join 엔드포인트로 회원가입 요청이 들어오면

  1. Gateway에서 아무런 권한이 없어도 UserService로 바로 라우팅한다.
  2. UserService에서 body를 토대로 UserDB에 회원을 추가한다.
  3. UserService에서 성공 혹은 예외 응답을 클라이언트에게 전달한다.

회원 테이블이라는 리소스에 변화가 있는 것이니, 아주 자명하게 UserService에서 이루어져야 하는 로직이라고 판단하였다.

🥑 로그인

/login 엔드포인트로 로그인 요청이 들어오면

  1. Gateway에서 아무런 권한이 없어도 UserService로 바로 라우팅한다.
  2. UserService에서 body를 토대로 UserDB에서 로그인 정보가 맞는지 확인한다.
  3. UserService에서 AccessToken과 RefreshToken을 발급하고, RefreshToken은 저장한다.
  4. UserService에서 두 개의 토큰을 클라이언트에게 전달한다.

이 부분이 처음에 가장 고민한 부분이다. Gateway에서는 모든 요청을 권한에 따라 쉽게 제한할 수 있도록 Spring Security를 사용하기로 하였다. Spring Security가 인증/인가를 위한 프레임워크이기 때문에, 인증(로그인) 과정도 Gateway에서 이루어져야 할 것 같은 근거 없는 느낌이 들었다.

그럼 Gateway에서 UserDB에 바로 접근해서 로그인 정보를 확인해야 하나? 생각이 들었다가, 서비스 분리의 의의를 완전히 잃어버리는 것이라고 판단하였다.

그럼 Gateway에서 로그인을 처리하는 과정에서 UserService로 로그인 정보가 맞는지 요청을 보내, 응답을 받고 이를 클라이언트에게 반환할까? 생각이 들었다. 로그인 정보가 맞으면 Gateway에서 토큰을 발급하는 것이다. 그런데 이는 충분히 UserService에서 처리 가능한 로직이기 때문에 Gateway 서비스에 다시 왔다가 다시 응답하는 것보다 바로 반환하는 것이 통신 오버헤드와 데이터 무결성을 고려하면 더 적합해 보였다.

🍉 RefreshToken 재발급

AccessToken이 만료되어 /reissue 엔드포인트로 RefreshToken과 함께 재발급 요청을 보내면,

  1. Gateway에서 아무런 권한이 없어도 UserService로 라우팅한다.
  2. UserService에서 header의 토큰을 바탕으로 RefreshToken이 유효한 지 판단한다.
  3. UserService에서 AccessToken과 RefreshToken을 발급한다.
  4. UserService에서 토큰을 클라이언트에게 전달한다.

로그인과 거의 비슷하다.

🍕 API 요청

/board/23 엔드포인트로 23번 게시글과 나의 좋아요 여부를 요청하면,

  1. Gateway에서 Header의 AccessToken 유효성 검사를 한다.
  2. Gateway에서 AccessToken의 사용자 정보를 바탕으로 UserService에 Passport를 요청한다.
  3. UserService에서 사용자 정보를 바탕으로 Passport를 발급한다.
  4. UserService에서 Gateway로 Passport를 전달한다.
  5. Gateway에서 Passport를 요청에 추가하여 BoardService로 라우팅한다.
  6. BoardService에서 Passport의 정보와 HMAC 해시값을 통해 유효한 요청인지 판단한다.
  7. BoardService에서 Passport의 정보로 게시글과 좋아요 여부를 BoardDB에서 가져온다.
  8. BoardService에서 게시글 정보를 클라이언트에게 전달한다.

Passport 구조

{
  "user": {
    "id": "1",
    "email": "amud@naver.com",
    "name": "amud",
    "role": "admin"
  },
  "userIntegrity": "HMAC-message"
}

세부내용은 달라지더라도, 데이터 무결성과 인증을 위하여 <정보-HMAC 해시값> 쌍은 가지고 있어야 메시지의 변조와 위장을 막을 수 있다. 이와 같은 방법을 사용하는 가장 대표적인 예시가 JWT 토큰이다.

관련 내용은 아래 포스트에 자세히 설명되어있다.

Netflix에서의 인증/인가 : [MSA] Netfilx는 MSA에서 어떻게 인증/인가를 처리할까? : Netfilx Passport

toss에서의 gateway : 토스는 Gateway 이렇게 씁니다

Netflix에서는 Gateway(Zuul)에서 passport를 발급한다고 언급되어 있고, toss에서는 Auth Service에서 발급을 한다고 언급되어있다. Gateway랑 UserService 중에서 어디서 발급하는 것이 맞을까 고민하였는데, Passport는 사용자 정보가 담긴 메시지이기 때문에 UserService에서 데이터를 꺼내와서 HMAC 해싱 처리하는 것이 메시지 변조와 위장을 막는 것이 더 확실할 것 같아 UserService에서 발급하기로 결정하였다.

🥕 정리

  • 각자 요구사항에 따라 얼마든지 달라질 수 있는 내용이다.
  • Gateway에서는 최대한 라우팅 관련된 로직만 있도록 한다.
  • 외부 토큰과 내부 토큰(Passport) 분리함으로써, 각 서비스의 인증/인가 부담을 줄이고 외부 토큰에 대한 의존을 끊을 수 있다.
profile
210's Velog :: Ambition Makes Us Diligent

1개의 댓글

comment-user-thumbnail
2023년 10월 30일

ㄷㄷㄷ 대박

답글 달기