지난 프로젝트에서도 API Gateway 패턴을 사용하였지만, 시간의 압박 때문에 중간중간 엉성하게 구현한 부분들이 있었다. 이번 프로젝트도 시간은 촉박했지만, 인증/인가 관련 프로세스만큼은 꼭 많은 고민 후에 확정하고 싶었다.
그래서 구체적인 프로세스를 아래와 같이 확정하였는데, 의외로 설계하는 방법이 다들 상이하였다. 고민한 과정을 공유하고 다양한 피드백을 받고자 게시글을 자세히 작성하였다.
완전 처음에는 Gateway에서 모든 인증/인가를 처리하는 것이 효율적이라고 생각했다. 그런데 Gateway도 하나의 서비스이고, 모든 요청이 Gateway를 거치고 가기 때문에 너무 큰 부담이 예상되었다.
특히 UserService는 서비스들 중에 트래픽이 크기 때문에 확장성을 고려해야 하는데, 단일 진입구인 Gateway와 분리하는 것은 꼭 필요해보였다. 물론 Gateway의 단일 장애 지점(SPOF)의 단점을 보완하기 위해 Gateway 자체에 대한 로드밸런싱이 있는 등의 여러 장치가 있지만, 트래픽이 큰 UserService와 분리하여 장애를 격리하는 것도 필요해보인다.
더 나아가 Gateway는 외부에 공개되기 때문에 보안 측면에서도 분리하는 것이 더 적합해보였다.
이후의 고민들이 대부분 GatewayService, UserService 중에서 어떤 서비스에서 처리해야 더 합리적인지 고민하는 내용이다.
/join
엔드포인트로 회원가입 요청이 들어오면
회원 테이블이라는 리소스에 변화가 있는 것이니, 아주 자명하게 UserService에서 이루어져야 하는 로직이라고 판단하였다.
/login
엔드포인트로 로그인 요청이 들어오면
이 부분이 처음에 가장 고민한 부분이다. Gateway에서는 모든 요청을 권한에 따라 쉽게 제한할 수 있도록 Spring Security를 사용하기로 하였다. Spring Security가 인증/인가를 위한 프레임워크이기 때문에, 인증(로그인) 과정도 Gateway에서 이루어져야 할 것 같은 근거 없는 느낌이 들었다.
그럼 Gateway에서 UserDB에 바로 접근해서 로그인 정보를 확인해야 하나? 생각이 들었다가, 서비스 분리의 의의를 완전히 잃어버리는 것이라고 판단하였다.
그럼 Gateway에서 로그인을 처리하는 과정에서 UserService로 로그인 정보가 맞는지 요청을 보내, 응답을 받고 이를 클라이언트에게 반환할까? 생각이 들었다. 로그인 정보가 맞으면 Gateway에서 토큰을 발급하는 것이다. 그런데 이는 충분히 UserService에서 처리 가능한 로직이기 때문에 Gateway 서비스에 다시 왔다가 다시 응답하는 것보다 바로 반환하는 것이 통신 오버헤드와 데이터 무결성을 고려하면 더 적합해 보였다.
AccessToken이 만료되어 /reissue
엔드포인트로 RefreshToken과 함께 재발급 요청을 보내면,
로그인과 거의 비슷하다.
/board/23
엔드포인트로 23번 게시글과 나의 좋아요 여부를 요청하면,
{
"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에서 발급하기로 결정하였다.
ㄷㄷㄷ 대박