[Project] [5] 서버가 여러대면 로그인은 어떻게 처리될까?

Hayoon·2024년 4월 11일
1

토이 프로젝트를 진행하면서 발생했던 문제에 대한 본인의 생각과 고민을 기록한 글입니다.
기술한 내용이 공식 문서 내용과 상이할 수 있음을 밝힙니다.

고민

프로젝트 중 문득 이런 생각이 들었다. WAS 서버가 여러대면 로그인 정보는 어디에 저장하는걸까?
결론부터 말하면 나의 프로젝트는 그런 고민을 할 필요가 없다. 토큰(JWT) 인증 방식을 채택했기 때문이다.

토큰 인증 방식

흔히들 알고 있는 JWT 토큰 방식으로 Refresh, Access Token 두 개를 가지고 주기적인 토큰 갱신을 통해 사용자 권한 인증/인가를 하는 방식이다.

단일 WAS 서버

다중 WAS 서버

토큰 인증 과정
1. 사용자 로그인: 클라이언트(브라우저)는 사용자의 ID와 비밀번호를 포함한 로그인 정보를 서버에 POST 요청으로 보낸다.
2. JWT 생성: 서버는 받은 로그인 정보를 검증한 후, 비밀키(secret)를 사용하여 사용자에 대한 JWT를 생성한다.
3. JWT 발급: 서버는 생성된 JWT를 클라이언트(브라우저)에게 보낸다.
4. JWT 저장 및 전송: 클라이언트는 받은 JWT를 저장(localStorage, sessionStorage 등)하고, 이후의 요청에 JWT를 포함하여 서버에 보낸다.
5. JWT 검증: 서버는 요청에 포함된 JWT를 검증(서명 확인 등)하고, 유효하다면 요청을 처리한다.

Nginx가 중간에 있다면?

여기서 Nginx 로드밸런서로 사용하고 있을 때의 과정은 기본적인 JWT 인증 흐름과 동일하다. Nginx 로드밸런싱을 통해 여러 WAS중 하나로 요청을 전달하는 역할을 한다.
JWT는 클라이언트와 서버 간에 교환되는 토큰이므로, Nginx나 로드밸런서는 JWT의 생성이나 검증 과정에 직접 관여하지 않는다. NGINX는 단순히 HTTP 요청을 적절한 서버로 전달하는 역할을 한다.

NoSQL Token Server

클라이언트는 Access Token 만료 시 서버에 재인증을 요청하고, Redis는 <K, V>로 저장된 <memberId, RefreshTokenExpiredTime>으로 Refresh Token 만료기간을 확인하여 유효한 경우 새로운 Access Token을 생성하여 클라이언트에게 반환한다. 이 방식은 클라이언트 측에서 Refresh Token을 직접 관리하지 않아도 되는 방식으로, 서버 측에서 보안을 효과적으로 관리할 수 있다.

Token 방식 TEST

WAS-1 또는 WAS-2 서버로 회원가입을 하고 로그인을 한 다음 회원 권한으로 특정 API(메뉴 리뷰 등록)를 여러번 호출해보자.

회원가입

로그인

메뉴 리뷰 등록

menu_id=7과 menu_id=11, 각기 다른 메뉴에 리뷰를 등록하였다.

Tomcat localhost-access-log

WAS-1

WAS-2

로그인 → menu_id=7 리뷰 등록 → menu_id=11 리뷰 등록 순서로 진행을 했다.

@PostMapping("/{menuId}/reviews")
ResponseEntity<MenuReviewDto.Response> menuReviewCreate(@RequestBody @Valid MenuReviewDto.Request request,
                                                        @MemberId Long memberId,
                                                        @PathVariable Long menuId) {
                                                        
    MenuReview menuReview = menuReviewService.create(memberId, menuId, request.comment(), LocalDateTime.now());
    MenuReviewDto.Response response = customMapper.map(menuReview, MenuReviewDto.Response.class);
    
    return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

리뷰 등록을 하기 위해서는 위의 Controller 로직처럼 Token이 필요하다. 따라서 여러 대의 WAS에서 토큰 값만 헤더에 넣어준다면, 각 요청에 포함된 JWT가 해당 요청을 인증하는 데 필요한 모든 정보를 담고 있기 때문에, 어느 WAS로 요청이 전달되더라도 해당 요청을 독립적으로 처리할 수 있는 것을 위의 사진을 통해 확인할 수 있다.

Database

세션 인증 방식

Sticky Session

Sticky session, 또한 session affinity라고도 불리는 방식은 사용자의 모든 요청이 같은 서버 인스턴스로 전송되도록 로드 밸런서를 구성하는 방식이다. 사용자의 세션 상태를 해당 서버의 메모리에 유지할 수 있으며, 서버 간 세션 정보를 공유할 필요가 없다.

하지만, 특정 사용자의 세션은 하나의 서버에 의존하고 있기 때문에 특정 서버에 트래픽이 집중되면 과부하가 발생할 수 있다. 또한, 세션을 가진 특정 서버가 다운되면 해당 서버의 세션 정보가 손실된다.

(출처: https://aws.plainenglish.io/what-are-sticky-sessions-5a20f7031eb4)

Session Clustering

Session Clustering은 여러 서버 인스턴스 간에 세션 상태를 공유하는 방식이다. 사용자의 요청이 어느 서버로 전달되더라도 같은 세션 정보에 접근할 수 있다. 세션 정보는 데이터베이스, In-Memory Data Grid, 또는 다른 중앙 집중식 스토리지 시스템에 저장될 수 있다.

IMDG(In-Memory Data Grid): 분산 컴퓨팅 환경에서 데이터를 메모리에 저장하고 관리하는 시스템. 데이터를 메모리 내에 저장하기 때문에, 디스크 기반의 저장 시스템에 비해 훨씬 빠른 데이터 접근 속도를 제공

하지만, 세션 정보를 모든 서버와 동기화하기 때문에, 서버 대수가 늘어남에 따라 추가 네트워크 트래픽과 저장소 사용량이 증가하고, 서버 메모리 활용의 비효율성 문제가 발생할 수 있다. 서버 대수가 지속적으로 늘어날 경우, 세션 정보를 저장하기 위한 물리적 메모리의 한계에 도달할 수 있다.

토큰 인증 방식이 무조건 좋을까?

토큰 인증 방식은 확장성, 유연성에서 우세하다. 로드밸런싱 환경에서 JWT를 사용하는 인증 방식은 상태를 서버에 저장하지 않는 방식이므로, 사용자의 세션 정보를 메모리에 저장하거나 세션 지속성을 고려할 필요가 없다.

하지만, 토큰의 치명적인 단점도 있다.

  1. 토큰 탈취가 세션 ID에 비해 취약하다. 토큰의 안전한 저장 및 전송, 적절한 만료 시간 설정이 필수적이다.

    세션 ID와 비교했을 때, 토큰(특히 JWT)은 자체적으로 사용자의 인증 정보를 포함하고 있어, 탈취되면 만료 시간까지 탈취자가 사용자의 권한을 가지고 행동할 수 있다.
    세션 ID의 경우, 서버 측에서 세션을 무효화할 수 있지만, 토큰은 서버 측에서 상태를 유지하지 않기 때문에 일단 발급된 토큰을 서버 측에서 무효화하기가 어렵다.

  2. 토큰 갱신 및 만료 처리를 위한 로직을 설계해야 한다. 사용자 경험에 직접적인 영향을 미치기 때문에, 사용자 불편을 최소화하는 방안을 찾아야 한다.

    토큰의 만료 시간이 너무 짧게 설정되어 있다면, 사용자는 자주 로그인해야 하는 번거로움을 겪을 수 있다. 모바일 앱과 같이 자동 로그인 기능이 중요한 경우에 사용자 경험을 저하시킨다.

결국, 어떤 인증 방식을 선택할지는 애플리케이션의 특정 요구 사항, 보안 요구 사항, 그리고 전체적인 아키텍처를 종합적으로 고려하여 결정해야겠다. (나는 팀원이 무지성 토큰 신봉자라 채택했다.)

profile
Junior Developer

0개의 댓글

관련 채용 정보