❓ 사전 배경 - 왜 이런 방법들이 생겼는가?
우리가 사용하는 웹 서비스에서 통신하는 프로토콜인 HTTP는 무상태성을 특징으로 갖는다. 즉, 내가 서버와 10번, 100번을 통신했더라도, 101번째 통신에서조차 서버는 나인지 모르고, 다시 내가 누구인지 밝혀야 하는 상황이라는 것이다.
이 상황은 이용자가 굉장히 불편하게 만든다. 내가 네이버로 메일을 보내려고 하는데, 메일함 누르는 순간, 메일 작성 버튼을 누르는 순간, 등등 매번 로그인을 하라고 한다면 귀찮지 않겠는가? (사실, 로그인을 했다고 해도, 로그인은 안 될 것이다)
이런 문제를 해결하기 위해, 다들 들어봤을 법한 쿠키, 세션, 토큰과 같은 방법으로 서버에게 사용자에 대한 정보를 지속적으로 준다. 사용자가 어떤 사용자인지, 사용자가 이 기능에 접근할 수 있는지 등등 말이다.
아무튼, 서비스를 운영함에 있어서, 사용자의 인증 정보를 관리하는 방법은 크게 두 방향으로 나뉘었다. 위에서 언급한 세션을 이용하는 방식과 토큰을 이용하는 방식으로 말이다. 두 방법에 대해서 알아보고, 각각의 방법이 어떤 특징을 갖는지에 대해서 이해하고, 자신의 프로젝트에서 더 알맞은 인증 방법을 선택하도록 하자.
세션 기반 인증 방법
세션 기반 인증 방법을 알기 위해서 먼저 세션에 대한 이야기를 하겠다.
세션은 서버에서 관리를 한다. 서버는 자기에게 접근하는 클라이언트에게 특정 ID를 부여하고, 해당 ID에 원하는 정보를 저장하는 것으로 그 클라이언트를 특정한다. 그렇다면 어떤 정보를 저장하는 것이 좋을까? 아무래도 '세션 기반 인증'이 제목인 만큼, 사용자를 인증할 수 있는 정보를 담으면 좋을 듯싶다. 예를 들면 사용자의 ID나 이메일과 같이 그 사용자를 특정할 수 있는 정보라면 좋겠다. 세션은 서버에서 관리하는 만큼, 저장된 정보가 외부로 노출될 가능성이 적고, 서버가 세션을 스스로 끊을 수 있기 때문에, 클라이언트의 행동이 의심이 간다면 조치를 취할 수 있어 보안적인 측면에서도 좋다고 한다.
하지만 서버에 데이터를 저장한다는 특징으로 인해, 서버의 자원을 이용한다는 단점이 있다. 또 다른 단점은 분산 환경에서의 세션 공유 문제가 있다. 세션은 하나의 서버 프로세스에 저장되기 때문에, 서버 인스턴스가 여러 개가 있다면, A서버에 세션 정보가 저장되어 있던 클라이언트가, B서버와 통신하게 되면 세션 정보를 읽지 못하게 된다는 문제가 있다. 이런 문제를 해결하기 위해 서버가 아닌 따로 세션 저장소를 두어 세션 정보를 관리하지만, 그로 인해 매번 세션 ID가 유효한지를 저장소와 통신해야 하기 때문에, 오버헤드가 발생하게 된다.
그리고 주의할 점으로는 보통 세션ID를 쿠키에 담아서 클라이언트가 서버에게 전송하는데, 중간에 쿠키가 탈취당하게 된다면 공격자는 자유롭게 서버와의 통신을 할 수 있기 때문에, 쿠키를 관리할 때 주의를 해야 한다.
세션 기반 인증 방법의 절차는 아래와 같이 이뤄진다.
1. 회원이 인증 절차(ex 로그인)를 진행한다. 인증이 성공했다면, 세션 ID를 만들어서 회원 정보를 세션 ID와 매핑하여 저장하고(서버 or 세션 저장소), 세션 ID를 가진 쿠키를 회원에게 전달한다.
2. 회원은 인증이 필요한 요청을 서버로 전달한다.
3. 서버는 요청의 헤더에서 쿠키를 꺼내고, 세션 ID를 조회한다. (서버 or 세션 저장소)
4. 세션 ID의 조회 유무로 인증 성공/실패를 판단한다.
절차에서 살펴봤듯이, 세션 기반 인증 방법을 사용하게 된다면, 매 요청마다 DB를 조회하게 된다. 이와 관련해서 ZUM에서 진행한 방법을 살펴보자.
https://zuminternet.github.io/spring-session/
ZUM에서는 중앙 세션 관리 서버를 따로 두고, 인증 정보를 그 서버에 요청하도록 했다. 그러나 해당 작업을 진행하면, 매번 인증 서버와의 통신과 Redis와의 통신 과정이 반복적으로 발생했고, 이 방법을 줄이기 위해서 캐싱을 도입했다고 한다.
세션 기반 인증을 사용할 때의 네트워크 I/O를 줄이는 자세한 방법과 Spring Session을 사용해서 세션 기반 인증을 도입한 내용에 대해서는 위의 링크를 통해 알아보도록 하자. 관련 내용을 추가로 작성한다면, 이후에 링크를 달아두도록 하겠다.
토큰 기반 인증 방법
토큰 기반 인증 방법 역시 토큰에 대해서 먼저 설명을 하겠다. 토큰이란 서버에서 특정한 비밀 키와 알고리즘등을 이용해서 만들어낸 것으로, 내부에 어떤 회원인지 식별할 수 있는 값을 지니고 있다. 주로 사용되는 토큰 방법으로는 JWT(JSON Web Token)이 있다.
토큰 기반 인증 방법의 절차는 아래와 같다.
1. 사용자가 인증(ex.로그인)과정을 마친다. 서버는 사용자를 식별할 수 있는 정보를 비밀키와 암호화 알고리즘을 통해 토큰을 제작하고 사용자에게 리턴한다.
2. 사용자는 전달받은 토큰을 저장한다.
3. 사용자는 인증이 필요한 요청에 토큰을 담아서 전송한다.
4. 서버는 토큰을 파싱해 유효한 토큰인지 체크하여 인증 성공/실패를 판단한다.
위에서 소개한 절차 중에서는 개발자마다 세부 구현 방법의 차이가 있는 글들이 있다.
첫 번째로는 토큰의 저장 위치이다. 토큰을 저장하는 위치에 대해서는 이 글을 작성하면서 조사했던 글에서는 브라우저의 로컬 스토리지에 저장하라고 했다. 또 다른 글에서는 HttpOnly Cookie에 저장하라는 말도 있었다.
두 번째로는 4번째 방법인 인증 성공/실패 판단 여부이다. 특정 글에서는 토큰을 사용한다면 토큰의 유효성 (만료 시간, 비밀 키가 동일한지)로만 성공/실패를 판단하지만, 일반적으로 JWT를 사용하는 예제들을 살펴보면 사용자 식별 정보를 DB에 조회해서 한번 더 확인하는 방법이 있었다.
이 두 차이에서는 더 합당해보이는 방법을 선택하는 것을 추천한다.
토큰 기반 인증 방식의 장단점을 알아보자.
토큰 기반 인증 방식의 장점은 세션 기반 인증 방식과 다르게 DB에 접근하는 횟수가 굉장히 줄어든다. 우선 토큰 자체를 검증하는 방식으로 1차적으로 걸러낼 수 있다. 예를 들면 사용자가 정말 이상한 값을 전달하더라도, 세션 방식의 경우는 해당 값이 있는지를 조회를 해봐야 하지만, 토큰 방식의 경우는 전달해 온 값이 유효한 형식인지 (주로 header, payload, signature 형태를 띤다), 아직 만료가 되지 않았는지, secret key가 일치한 지를 판단을 우선한다. 인증 방식이 간단하기 때문에, 토큰 기반 방식을 이용한다면 인증을 처리하는 과정의 비용이 줄어들어 성능 향상에 도움이 될 것이다.
토큰 기반 방식의 단점은 토큰이 탈취당했을 때의 문제가 크다는 점이다. 세션 역시 쿠키를 탈취당하면 문제가 발생하지만, 서버에서 세션을 관리하기 때문에, 세션을 끊을 수 있다. 그에 반면 토큰은 인증 방식이 간단하여, 어떤 유저가 요청을 보내는지 정확히 판단할 수 없으며, 문제가 발생하더라도 해당 토큰이 만료되기 전까지는 공격자가 자유롭게 서버로 요청을 보낼 수 있다는 문제가 있다.
결론
지금까지 세션 기반 인증 방법과 토큰 기반 인증 방법에 대해서 알아보았다. 지금까지 개발하면서 항상 `JWT` + `Spring Security`를 이용한 방법이 대세고, 세션을 이용하는 방법보다 무조건 좋다고 생각했었다.
하지만 조사를 통해 알아보니, 두 방법은 각자 장, 단점이 존재하고 어떤 방법 하나가 더 우월한 방식은 아니라는 것을 알게 되었다. 그리고 각자의 방법의 단점에서 발생하는 부분을 해결하기 위해서 세션의 경우는 캐싱을 도입하고, 토큰의 경우는 엑세스 토큰의 유효 기간을 짧게 가져가는 등의 방식을 이용한다. 어떤 방식이든 그 단점을 해결하려는 개발자들의 노력이 있으니까, 어떤 방법이 우월하다는 생각은 버리고, 두 방법의 특징을 기억하고 자신의 서비스에 더 적합한 방법을 이용하도록 해보자.
참고 자료
https://zuminternet.github.io/spring-session/
https://www.geeksforgeeks.org/session-vs-token-based-authentication/
https://docs.spring.io/spring-session/reference/index.html