개발자 이야기/Spring

로그인 방식에 대한 고찰 : JWT vs Session

oneny 2023. 11. 25.
728x90
반응형

JWT vs Session

보통 로그인 방식을 어떤 방식으로 구현했는지 보면 JWT를 사용한 토큰 기반 인증방식 또는 Session을 사용한 세션 기반 인증방식이 있다. 그리고 주위 개발자분들께 회사에서 어떠한 방식을 쓰는지 여쭤보면 JWT 방식을 사용하고 있다고 있다고 한다. 근데 왜 JWT를 사용하는지에 대해서는 쉽게 말씀해주시지 못하는데 이번 기회를 통해 각 방식의 장단점에 대해 비교해보고, 현재 하고 있는 프로젝트에 어떤 방식을 선택해 사용할지 고민해보자.

 

JWT(Json Web Token)

 

JSON Web Token(JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature(JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code(MAC) and/or encrypted.

출처: JSON Web Token (JWT)

 

JWT에 대해 간단히 살펴보면 Header, Payload, Signature 세 부분을 '.(dot)'을 구분자로 하여 JWT 토큰 1개를 이룬다. 위 내용을 보면 JWT의 클레임은 디지털 서명되거나 메시지 인증 코드(MAC)로 무결성 보호될 수 있다고 한다. 여기서 무결성이라고 하는 것은 메시지가 변경되지 않았음을 증명하는 것, 즉 쉽게 말해서 클라이언트 측에서 보낸 메시지가 서버에서 송신한 메시지와 동일함을 증명하는 것이다.

어떻게 메시지 무결성을 증명할 수 있을까? 이는 JWT에서 Signature 덕분이라고 할 수 있다. Signature는 Base64 인코딩된 Header.Payload와 비밀 코드(secret)를 가지고 해시 함수를 통해 만들어진다. 따라서 잘 알려진 해시 알고리즘을 사용하더라도 중간에 JWT를 탈취해서 Payload 값을 바꾸려 한다면 비밀 코드를 모르기 때문에 서버에서 받은 JWT의 시그니처의 값과 서버에서 보낸 JWT의 시그니처 값이 달라 안전하다고 할 수 있는 것이다. 이러한 해시 함수와 비밀키를 사용하는 암호화 인증 기술을 HMAC(Hash-based message authentication code)라고 한다.

 

 

accessToken을 localStorage에 저장하지 말라고?

글 작성 계기 이전에 쇼핑몰 프로젝트를 한 경험이 있다. 이 때 백엔드 분이 jwt 방식으로 accessToken은 json으로 보내주셨는데 이 처리를 어떻게 해야 할지 모르겠어서 그냥 localStorage에 저장했다.

velog.io

이전 프론트엔드 공부하던 때에 프로젝트에 JWT를 사용해서 access token과 refresh token을 통해 로그인 방식을 구현했었다. 확실히 DB를 조회할 필요없이 access_token 토큰 자체로 인증을 할 수 있고, 서버 이중화를 해야 한다고 확장성에서도 이점이 있다고 할 수 있다. 하지만 만약 블랙리스트를 조회해야 할 일이 있다면? Redis 같은 In-memory DB를 사용하면 JWT의 이점이 크다고 할 수 있을까? access_token을 통해 DB 조회해야할 일이 없었지만 블랙리스트인지 확인해야 한다면 결국은 Session 방식과 마찬가지로 DB를 조회해야할 일이 있고, 추가로 JWT는 암호화 과정이 필요하면서 Why JWTs Suck as Session Tokens 게시글을 보면 단순히 JWT에 iss, sub, nbf, exp, iat, jti, typ 클레임만을 실었는데 304 바이트가 나오고, 그에 비해 session id는 6바이트 50배가 넘는 트래픽 비효율을 감안하면서까지 이점이 있는지 고민해볼 필요가 있다.

 

Session

The servlet container uses this interface to create a session between an HTTP client and and HTTP server. The session persists for a specified time period, across more than one connection or page request from the user. A session usually corresponds to one user, who may visit a site many times. The server can maintain a session in many ways such as using cookies or rewriting URLs.

출처: Interface HttpSession

 

위 내용을 살펴보면 세션은 사용자가 웹 브라우저를 통해 웹 서버에 접속한 시점으로부터 웹 브라우저를 종료하여 연결을 끝내는 시점까지 같은 사용자로부터 오는 일련의 요청을 하나의 상태로 보고 그 상태를 일정하게 유지하는 기술이다. 이러한 세션은 Tomcat이라는 JAVAEE(엔터프라이즈 어플리케이션을 위해 만든 표준으로 수많은 클래스/인터페이스로 정의되어 있음) 스펙의 일부를 구현한 Servlet Conatiner가 생성한 인스턴스이다. 이러한 세션은 다음과 같은 특징이 있다.

  • Browser마다 개별 저장소(Session 객체)를 서버에서 제공(발급)한다.
  • 각 클라이언트에게 고유한 ID를 부여한다.
  • 웹서버에 컨테이너 상태를 유지하기 위한 정보를 저장한다.
  • 세션 ID로 클라이언트를 구분하여 클라이언트 요구에 맞는 서비스를 제공한다.
  • 사용했던 정보들을 서버에 저장하기 때문에 쿠키보다 보안성이 더 우수하다.
  • 서버에 저장되기 때문에 서버 부하가 발생한다.
  • HTTP 프로토콜은 비연결성, 무상태성 특징이 있기 때문에 매 접속마다 새로운 네트워크 연결이 이뤄지는데, 세션이 연결 유지를 가능하게 해준다.

JWT와 Session 인증 방식 차이

JWT는 위에서 살펴봤듯이 헤더, 페이로드, 시그니처 필드로 나뉘며 페이로드는 암호화되어 있지 않고 URL-safe하도록 base64 인코딩이 되어있기 때문에 누구나 페이로드를 읽을 수 있기 때문에 민감한 데이터를 실어서는 안된다. 하지만 비밀키를 이용하여 만든 Signature 덕분에 토큰 자체를 통해 인증이 가능하다는 것이 장점이라고 할 수 있다.

세션 ID는 그 자체로는 단순히 난수 문자열이기 때문에 어떤 의미를 갖지 않는다. 또한 세션의 경우 모든 인증 정보를 서버에서 관리하기 때문에 보안 측면에서 조금 더 유리하다. 설령 세션 ID가 해커에게 탈취된다고 하더라도, 서버 측에서 해당 세션을 무효 처리하며 된다. 하지만 토큰의 경우에는 서버가 트래킹하지 않고, 클라이언트가 모든 인증 정보를 가지고 있기 때문에 토큰을 한 번 해커에게 탈취되면 해당 토큰이 만료되기 전까지는 속수무책으로 피해를 입을 수 밖에 없다.

 

그럼에도 불구하고 웹 어플리케이션에서 토큰 기반 인증을 JWT로 많이 사용하는 이유는 확장성이다. 일반적으로 웹 어플리케이션의 서버 확장 방식은 Scale Out으로 여러 대의 서버가 요청을 처리하게 된다. 이 때, 토큰 인증 방식은 서버가 직접 인증 방식을 저장하지 않고, 클라이언트가 저장하는 방식을 취하기 때문에 확장성에 용이하지만, 세션 기반 인증 방식은 세션 불일치 문제를 겪을 수 있다. 따라서 이를 해결하기 위해 Sticky Session, Session Clustering, 세션 스토리지 외부 분리 등의 작업을 해줘야 한다.

 

Sticky Session

Sticky Session은 로드 밸런싱을 사용하는 웹 서버 환경에서 클라이언트 요청을 처리하는 방식 중 하나다. Sticky Session은 위 그림처럼 특정 사용자의 모든 요청을 항상 동일한 서버로 보내도록 하는 메커니즘을 말한다. 이는 세션 정보나 쿠키를 사용하여 이뤄질 수 있다. 하지만 이러한 방식에는 다음과 같은 단점이 있습니다.

  • 장애 대응의 어려움: Sticky Session은 특정 서버에 연결된 클라이언트가 해당 서버에 문제가 발생하여 다운되면 해당 클라이언트는 서비스를 이용하지 못하게 될 가능성이 크다. 이를 해결하려면 로드 밸런서가 서버 장애를 감지하고 다른 서버로 클라이언트 요청을 재분배해야 한다.
  • 부하 분산 활용의 어려움: Sticky Session을 사용하면 특정 클라이언트가 항상 특정 서버에 연결되므로, 특정 서버에 계속해서 연결되어 특정 서버에 많은 트래픽이 집중되면 해당 서버는 다른 서버보다 더 많은 작업을 처리해야 하므로 성능 이슈가 발생할 수 있다. 즉, 서버 간의 부하 분산이 최적으로 이뤄지지 않을 수 있다. 

 

Session Clustering

Session Clustering은 여러 서버 간에 세션 정보를 공유하고 동기화하여 사용자의 세션 상태를 유지하는 기술을 말한다. 위 그림처럼 분산된 환경에서 세션 정보의 일관성을 유지하기 위해 사용된다. 따라서 특정 사용자가 Server1이든 Server2이든 Server3이든 새로운 서버가 추가되더라도 세션이 공유되기 때문에 트래픽을 분산하여 처리할 수 있다는 장점이 있다. 즉, 특정 서버에 장애가 발생하더라도 다른 서버에 세션 정보가 동기화되어 있기 때문에 서비스를 계속해서 이용할 수 있다. 하지만 Session Clustering에도 다음과 같은 단점이 있다.

  • 서버 메모리 부담: 모든 서버에서 세션 정보를 공유하고 동기화해야 하기 때문에 서버 메모리 부담이 생길 수 있다. 
  • 서버 간 통신 오버헤드: Session Clustering은 구현 및 유지 관리가 비교적 복잡하다. 또한 세션을 동기화하기 위해 서버 간에 세션 데이터를 주고받아야 하므로 네트워크 오버헤드가 발생한다. 이러한 Session Clustering은 서버 간 동기화 및 통신에 대한 문제를 다뤄야 하며, 이로 인한 디버깅과 유지 관리가 어려울 수 있다.

 

Session Storage

세션을 외부 스토리지로 분리하는 작업은 외부 데이터베이스를 사용하여 세션을 관리하는 방식을 말한다. 이러한 방식은 Sticky Session처럼 한 서버에서 트래픽 부담할 상황이 일어나지 않고, 서버를 추가하더라도 세션 스토리지를 통해 공유하기 때문에 서버들끼리 서로 네트워크하지 않고, 서버 메모리에 부담없이 확장할 수 있다는 이점이 있다.

이렇게 외부 세션 스토리지를 관리할 수 있도록 도와주는 모듈 Spring Session이 있다. Spring Session을 사용하면 기존의 HttpSession을 유지하면서 Redis, MongoDB, JDBC 등의 외부 스토리지를 사용하여 세션 데이터를 저장하고 관리할 수 있다.

 

Spring Session

스프링 세션은 스프링 프레임워크에서 제공하는 모듈 중 하나로 기존의 HttpSession을 대체한다. 위에서 본 확장에 문제가 있는 문제를 해결하기 위해 Redis, JDBC 등 외부 저장소를 사용하게끔 하여 분산된 스케일링 가능한 환경에서 세션 데이터를 관리할 수 있도록 해준다. Spring Session이 설정되면 Servlet Container가 생성한 구현체(HttpSession 구현체)가 아니라 SpringSession이 생성한 구현체가 주입될 것이다. 스프링 세션은 HttpSession 인터페이스의 자체적 구현을 제공하며 어떠한 변경사항 없이 서블릿과 필터는 여전히 HttpSession API를 동일하게 이용할 수 있다.

즉, Spring Session을 사용함으로써 분산 환경에서 세션 관리가 쉬워지고, NoSQL의 키-값의 In-memory 데이터베이스를 사용하면 어플리케이션 퍼포먼스를 향상시킬 수 있기 때문에 JWT나 세션이나 큰 차이가 없고 오히려 JWT의 장점이 점점 없어진다. 또한 Spring Session을 사용하면 Tomcat과 같은 특정 컨테이너에 종속적이지 않고 RESTful API 통신에 활용하는 경우를 위해 쿠키가 아닌 헤더 기반 세션 ID 기능도 지원한다는 것이다.

따라서 프로젝트를 진행하면서 In-memory DB인 Redis를 세션 스토리지로 Spring Session을 사용한 인증 방식을 선택하기로 했다.

 

JDBC vs Redis

JDBC는 MySQL, PostgreSQL 등과 같은 데이터베이스를 선택해서 세션 데이터를 관리하겠다는 것인데 이는 유저가 많아지면 많아질수록 디스크 기반의 저장소이므로 상대적으로 메모리 기반 저장소에 비해 성능이 떨어진다는 단점이 있다.

Redis는 메모리 기반이므로 메모리 소비가 크다는 단점이 있지만 매우 빠르게 동작하며, 세션 데이터의 처리와 검색에 특히 용이하다. 또한 Redis는 각 키에 대한 유효시간을 설정하여 데이터의 자동 삭제가 가능하기 때문에 세션의 만료를 자동으로 처리할 수 있다. 따라서 세션 클러스터용 스토리지를 구성하고자 할 때 Redis가 더 좋다고 할 수 있다.

 

출처

Authentication: JWT usage vs session

API 서버의 인증 수단으로 JWT를 사용하는 것이 옳은가?

세션 기반 인증과 토큰 기반 인증(feat. 인증과 인가)

스프링 세션(Spring Session)이란 무엇이고 어떻게 작동할까?

 

728x90
반응형

'개발자 이야기 > Spring' 카테고리의 다른 글

Redis로 Session Store 적용하기  (0) 2023.11.28

댓글

추천 글