대표적으로 사용자의 인증/인가를 처리할 때 사용하는 방식이 다음과 같이 3가지가 있습니다.
1. 쿠키 방식
2. 세션 방식
3. JWT 토큰 방식
위의 3가지 방식들의 장단점을 살펴보고, 무엇을 사용해야 할지 알아보도록 합시다!
1. 쿠키 인증 방식 - 클라이언트가 인증 정보 관리 (stateless)
먼저, 쿠키 인증 방식입니다.
쿠키 인증 방식은 쿠키에 사용자의 인증 정보를 담아서 인증하는 방식입니다.
인증/인가 Flow는 다음과 같습니다.
1. 클라이언트가 서버에 첫 로그인 인증 요청을 보내면, 서버에서 응답으로 쿠키에 사용자 인증 정보를 담아서 보낸다.
2. 서버에서 응답한 쿠키를 클라이언트에서 저장하고, 인증/인가 요청 시마다 서버로 요청한다.
Flow만 보더라도, 인증/인가 작업이 상당히 쉽고 간단한 것을 알 수 있습니다.
또 사용자의 인증 정보를 클라이언트가 관리하기 때문에 서버 부하가 적다는 장점이 있고,
인증 상태를 서버가 관리하지 않고 매번 클라이언트의 인증 정보를 담은 쿠키의 요청을 받을 때 처리하므로 Stateless 합니다.
위와 같은 특징 및 장점이 있지만, 쿠키 인증 방식에서는 치명적인 단점이 존재하기 때문에 3가지 방식 중 거의 사용되지 않고 있습니다.
쿠키의 치명적인 단점은 다음과 같습니다.
- 클라이언트(사용자)가 쉽게 쿠키에 담긴 인증 정보를 위조할 수 있다.
- 쿠키의 데이터 크기가 제한적이고, 또 크기가 커진다면 네트워크 부하가 심해진다.
2번의 이유도 치명적이지만, 1번의 이유가 쿠키 방식을 사용하지 않는 치명적인 이유라고 할 수 있습니다.
해당 문제의 키워드로는 CSRF, XSS 공격 등이 있지만,
여기서는 다루지 않고 쿠키 방식은 보안상 상당히 위험하다는 것만 알고 넘어가도록 하겠습니다!
2. 세션 인증 방식 - 서버가 인증 정보 관리 (stateful)
다음으로는, 세션 인증 방식입니다.
세션 인증 방식은 세션으로 사용자의 인증 정보를 관리하는 방식입니다.
인증/인가 Flow는 다음과 같습니다.
1. 클라이언트가 서버에 첫 로그인 인증 요청을 보내면, 서버에서 인증 정보를 저장하고 SessionID를 클라언트에게 제공
2. 서버에서 받은 SessionID로 서버에게 인증/인가 요청을 하여 서버에서 SessionID에 해당하는 인증 정보로 인증/인가 처리
Flow에도 나와 있듯이, 세션 인증 방식은 사용자의 인증 정보를 클라이언트가 아닌 서버가 관리하게 됩니다.
서버에서 인증 정보를 관리하기 때문에 보안상 훨씬 안전하다는 장점이 있습니다.
또한 다음과 같은 추가 기능을 제공할 수 있다는 장점이 있습니다.
1. 한 사용자의 디바이스별 인증을 관리할 수 있다.
- PC로 접속 시 다른 기기(모바일, 태블릿 등)의 접근을 막을 수 있다.
- 여러 디바이스에서 접속 중일 때 특정 디바이스의 유저를 로그아웃하게 할 수 있다. (인스타그램)
2. 하나의 계정 공유를 관리할 수 있다.
- 넷플릭스처럼 계정 공유의 수도 제한할 수 있다.
3. 비정상적인 접근 신고가 들어오면, 서버에서 판단하여 해당 세션을 삭제해서 바로 로그아웃시킬 수 있다.
이때, 서버는 내부 어느 곳에서 사용자 인증 정보를 관리할까요?
사용자 인증 정보를 관리하는 곳은 크게 메모리, 하드 디스크, DB 이렇게 3가지입니다.
3가지 중 메모리, 하드디스크, DB에서 관리하는 방식을 비교하고 장단점을 알아봅시다.
2-1. 메모리 영역/하드디스크에서 사용자 인증 정보 관리
먼저 메모리 영역에서 사용자 인증 정보를 관리하게 되면, 뒤에 나올 나머지 2가지 방식보다 속도가 빠릅니다.
하지만, 단점으로 사용자가 동시에 SessionId로 인증 요청을 많이 보내게 되면 서버 메모리가 부족해지고,
서버를 껐다 켰을 때 Session 정보가 휘발된다는 치명적인 단점이 존재합니다.
두번째로 하드디스크에서 관리하게 되면 메모리 영역보다 속도는 느리지만 DB보다는 속도가 빠릅니다.
메모리, 하드디스크 영역에서 관리하게 됐을 때의 공통적인 문제점은 다음과 같습니다.
Scale-out을 사용하여 서버가 여러 대 존재하여 로드 밸런싱을 사용할 때,
한 사용자가 로그인 후 다음 인가 요청 시 로드 밸런싱으로 인해
사용자가 로그인한 서버가 아닌 다른 서버로 요청이 요청이 보내지면 다시 재로그인해야한다.
간략하게 설명하자면, 서버 부하를 관리하는 로드 밸런싱을 설정해놓은 상황에서 인증/인가 요청을 보내는 상황입니다.
이때 로그인 요청은 서버 A로 보내서 SessionID에 맞는 사용자 인증 정보를 서버 A에 저장하고,
서버 A에서 생성한 SessionID를 사용자에게 응답해줍니다.
그 후에, 인가 요청 시 사용자가 발급받은 SessionID로 서버에게 인가 요청을 합니다.
이때, 부하가 A에 몰려있어서 인가 요청을 로드 밸런서가 서버 B로 보내게 될때 문제가 발생합니다.
서버 B에는 해당 SessionID가 존재하지 않기 때문에 로그인하지 않은 사용자로 판단하여 SessionID를 생성하여 응답합니다.
로드밸런싱 시에 이러한 문제가 발생하여 추가적인 서버 부하와 사용자에게 불편함을 줄 수 있습니다.
로드밸런서로 Nginx를 사용한다면 IP Hash를 통해 지정한 IP는 항상 서버 A에게 요청을 보내서 해결할 수 있지만,
이렇게 설정한다면 로드밸런싱을 하는 의미가 퇴색되는 느낌을 받아서 좋은 방법은 아니라고 개인적으로 생각합니다.
2-2. 서버 DB에서 사용자 인증 정보 관리
서버 DB에서 사용자 인증 정보를 관리하는 방법입니다.
속도가 위의 2가지 방법보다 느리지만, 위에서 말한 단점들을 모두 해결할 수 있습니다.
그래서 인증/인가 방식으로 세션을 이용한다면 보통은 서버 DB로 사용자 인증 정보를 관리합니다!
하지만 로그인한 모든 유저의 인증 정보를 DB에 관리해야하므로 무겁고,
매 인증마다 DB를 거쳐 인증 정보를 Select 해야하므로 연결 비용이 크다는 단점이 존재합니다.
세션 인증/인가 방식을 요약해보자면,
서버가 인증 정보를 관리하기 때문에 보안상 안전하고 여러 추가 기능들을 사용할 수 있었습니다.
하지만, 서버에서 관리하므로 서버 부하가 상대적으로 클 수 있다는 단점이 있었습니다.
3. JWT 토큰 방식 - 클라이언트가 인증 정보 관리 (stateless)
마지막으로 토큰 방식입니다.
토큰의 종류에는 JWT 이외에도 여러 토큰이 존재하지만 JWT 토큰이 간단하고 레퍼런스가 많기 때문에
JWT 토큰을 사용하는 인증 방식으로 설명하도록 하겠습니다!
인증/인가 Flow는 다음과 같습니다.
1. 클라이언트가 서버에 첫 로그인 인증 요청을 보내면, 서버에서 응답으로 Token을 담아서 보낸다.
2. 서버에서 응답한 Token을 클라이언트에서 저장하고, 인증/인가 요청 시마다 서버로 요청한다.
기본적으로는 클라이언트가 인증 정보를 관리하기 때문에 쿠키 인증 방식과 Flow가 비슷합니다.
차이가 있는 부분은, 쿠키가 아닌 JWT 토큰을 매개체로 인증한다는 것과 서버에서 '토큰 검증'이 이루어진다는 점입니다.
이를 통해 쿠키보다는 보안적으로 안전하게 관리될 수 있습니다.
어떻게 쿠키보다 보안적으로 안전할까요?
이는 이해하기 위해서는 JWT를 알아야 합니다.
관련해서 제 블로그에 JWT에 대해 정리한 글이 있기 때문에 자세한 JWT 설명은 아래 링크를 참고해주시면 감사하겠습니다!
https://ksh-coding.tistory.com/58
JWT를 간단하게 설명하면, 사용자의 인증 정보와 서버의 SecretKey로 서버가 생성한 Token입니다.
여기서 중요한 점은 '서버의 SecretKey로 서버가 생성한 Token'이라는 점입니다!
클라이언트가 Token을 저장하고 인가 요청 시에 발급받은 Token을 사용하여 요청을 보내기 때문에
Token을 클라이언트가 관리한다는 점은 쿠키 인증 방식과 비슷하지만,
서버에서도 해당 Token에 대한 SecretKey를 가지고 있다는 점에서 쿠키 인증 방식과 다릅니다.
앞에서 말한 '토큰 검증'이라는 것이 바로 요청으로 온 Token이 서버에서 발급한 Token인지 검증하는 단계입니다.
이때, 요청으로 온 Token의 SecretKey가 다르다면 위조, 변조된 Token이라고 판단하여 인증 에러를 발생시키게 됩니다.
이러한 검증 단계를 거치기 때문에 쿠키 인증 방식보다는 보안상 훨씬 안전합니다.
또, 세션 방식과 비교해서는 기본적으로 클라이언트가 Token을 관리하기 때문에 서버 부하가 비교적 덜하다는 장점이 있습니다.
하지만, Token을 검증하는 것뿐이지 해당 Token을 관리하는 것이 아니기 때문에 세션보다 보안상 취약하고
비정상적인 유저라고 판단 시 Token을 만료하거나, 디바이스별 제어 등 세션에서 가능했던 부분들이 불가능합니다.
위에서 설명한 Token은 모두 AccessToken을 의미하고 물론 RefreshToken을 사용하면 가능해지긴 하지만
세션 방식에 비해서는 완벽하지 못합니다.
토큰 인증/인가 방식을 요약해보자면,
클라이언트가 인증 정보를 관리한다는 점에서는 쿠키 인증 방식과 비슷하지만,
쿠키 대신 JWT 토큰을 사용하고, 서버에서 해당 토큰을 검증하는 단계를 거쳐 보안상 쿠키 방식보다 뛰어나고,
서버 부하가 비교적 덜 하다는 장점이 있었습니다.
하지만, 보안이 세션 방식 보다는 취약하고 세션 방식의 여러 기능들을 사용할 수 없다는 단점이 존재했습니다.
4. 여러 인증/인가 방식 중 무엇을 사용해야 할까?
위에서 쿠키, 세션, 토큰(JWT) 방식의 장단점들을 살펴봤습니다.
그렇다면 셋 중에 어떤 방식을 사용하는 것이 좋을까요?
개인적인 저의 생각을 말해보도록 하겠습니다!
우선 일반적으로 쿠키 인증 방식은 보안상 취약점이 너무 많고 크리티컬하기 때문에 보통 사용하지 않습니다.
따라서 보통 인증/인가 방식을 고민한다면 세션 방식을 사용할지, JWT 토큰 방식을 사용할지 고민하게 됩니다.
저는 우선 비즈니스적으로 세션의 추가 기능을 사용해야할지 생각해볼 것 같습니다.
앞서 세션 방식 설명 시 예시로 나왔던 넷플릭스, 인스타그램 같이 계정 공유 제한, 디바이스별 로그아웃 기능들이
비즈니스적으로 있어야만 하는 핵심 기능이라면 세션 방식을 사용해서 간편하게 처리하고,
세션 방식의 문제인 Scale-out 시 처리나 서버 부하 등의 부분을 개선해나갈 것 같습니다.
(물론 토큰 방식으로도 위의 기능들을 구현할 수 있지만 세션 방식이 더 편하므로 세션을 사용할 것 같습니다.)
하지만 만약 비즈니스적으로 위와 같은 기능이 필요하지 않다고 생각하면 토큰 방식을 적용할 것 같습니다.
Scale-out 시에 고려해야하는 리소스가 세션 방식보다 토큰 방식이 훨씬 적다고 생각했기 때문입니다.
보안상으로는 완벽하지 못하지만 보안을 보완하는 방식들이 몇 가지 존재하고, 해당 방식을 사용하면
어느정도 수준까지는 보안성을 높일 수 있다고 생각합니다!
그래서 저의 생각을 요약해보자면,
일반적으로 토큰 방식을 사용하고 비즈니스적으로 세션의 기능을 사용해야한다거나 보안이 정말 중요하다면
세션 방식을 사용해볼 것 같습니다!
(지식이 얕은 현재 저의 생각이기 때문에 나중에는 기준이 조금 바뀔 수는 있을 것 같네요 ㅎㅎ 🌟)
Reference
[얄팍한 코딩 사전 - 세션 VS 토큰! JWT가 뭔가요?]
https://www.youtube.com/watch?v=1QiOXWEbqYQ&ab_channel=%EC%96%84%ED%8C%8D%ED%95%9C%EC%BD%94%EB%94%A9%EC%82%AC%EC%A0%84
[연로그 테코톡 - 쿠키 VS 세션 VS 토큰 VS 캐시]
https://www.youtube.com/watch?v=1QiOXWEbqYQ&ab_channel=%EC%96%84%ED%8C%8D%ED%95%9C%EC%BD%94%EB%94%A9%EC%82%AC%EC%A0%84