과정을 즐기자

로그인 방식 비교하기 (세션 방식 VS 토큰 방식) 본문

Network

로그인 방식 비교하기 (세션 방식 VS 토큰 방식)

320Hwany 2023. 8. 6. 18:35

이번 글에서는 Stateless인 HTTP에서 로그인을 어떻게 유지하는 지에 대해서 생각해보며 성능을 고려하여

세션 기반 인증, 토큰 기반 인증을 비교해보겠습니다.   

세션 기반 인증

세션을 저장하는 곳

세션 기반 인증은 서버측에서 사용자들의 정보를 기억해야 합니다. 

사용자가 로그인 요청을 하면 서버에서는 로그인 할 수 있는 회원인지 확인한 후 로그인 가능하면 Set-Cookie에 

Session Id를 반환합니다. 그러면 사용자의 다음 요청시 쿠키의 헤더를 보고 서버는 사용자가 로그인 유지를 할 수 있도록 합니다.

 

하지만 이때 문제점이 있습니다. 사용자의 세션 정보는 서버가 가지고 있는데 트래픽이 늘어나 서버가 여러 대로 늘어난다면 

사용자는 로그인을 요청했던 서버에게 요청을 할 때만 로그인을 유지할 수 있다는 점입니다.  

 

사용자가 Server1에 로그인을 요청했었다면 다음 요청들도 Server1에 요청을 해야지만 로그인이 유지됩니다.   

이에 대한 해결 방법으로 Stick Session 방식으로 특정 세션의 요청을 처음 처리한 서버로만 전송하는 방식이 있습니다.   

하지만 로드 밸런서가 제대로 동작하지 않을 수 있고 특정 서버에게 부하가 몰릴 수 있기 때문에 바람직한 방법은 아닙니다.   

 

다른 방식으로는 서버 계층은 사용자의 상태를 저장하지 않는 Stateless한 방식을 유지하고 DB에 세션을 저장할 수 있습니다.

 

하지만 이 방식은 DB에 세션을 저장해야 하기 때문에 세션을 위한 DB 저장공간을 할당해야 하며  

요청이 많아지면 DB 부하가 많아질 수 있다는 단점이 있습니다. 

DB 요청이 많아지면 세션 저장소로 RDB가 아닌 Redis와 같은 인메모리 DB를 고려해볼 수 있습니다.

쿠키

다음으로 쿠키에 대해 생각해보겠습니다. 세션 방식은 사용자가 요청할 때 쿠키의 헤더를 보고 로그인 여부를 판단합니다.

로그인을 한번 하면 로그인이 필요없는 요청에도 쿠키가 포함되어 보내집니다.

따라서 HTTP 헤더에 불필요한 데이터가 늘어난다는 단점이 있습니다.

 

또한 쿠키 방식은 CSRF, XSS 공격 문제가 있습니다.

CSRF(Cross-Site Request Forgery)는 사이트 간 요청 위조라고 불리며 악의적인 요청을 사용자가 의도하지 않은 상태에서

사용자의 권한으로 실행하는 것을 말합니다.

XSS(Cross-Site Scripting)은 사이트 간 스크립팅이라고 불리며 악의적인 스크립트가 웹 페이지에 삽입되어 사용자

브라우저에서 실행되는 공격을 말합니다.

 

CSRF 공격을 방지하는 방식으로는 SameSite 쿠키 속성을 사용하여 같은 사이트에서만 쿠키를 전송할 수 있도록 제한하고

Referer Policy를 설정하여 요청에 대한 Referer 헤더를 제어하는 방식이 있습니다

XSS 공격을 방지하는 방식으로는 HttpOnly 속성을 사용하여 쿠키를 자바 스크립트로 접근할 수 없도록 하는 방식이 있습니다.

이외에도 Secure 속성으로 쿠키가 암호화된 HTTPS 연결을 통해서만 전송되도록 할 수 있습니다.  

 

토큰 기반 인증

JWT란?

토큰 기반 인증은 가장 많이 사용하고 있는 JWT(Json Web Token)를 기준으로 설명하겠습니다.   

JWT는 Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token 입니다.

JWT는 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 전달합니다.

 

JWT는 Header, Payload, Signature 3부분으로 나뉩니다. Json 형태인 각 부분은 Base64Url로 인코딩되어 표현됩니다.

각 부분은 . 구분자를 사용하여 구분합니다.

공식문서에서 인코딩된 JWT를 하나 가져와봤습니다. 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

 

앞 부분인 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9가 header를 나타내고 디코딩을 해보면 아래와 같습니다.

{
  "alg": "HS256",
  "typ": "JWT"
}

alg는 Signature 및 토큰 검증에 사용하고 typ은 토큰 타입을 지정합니다.  

 

중간 부분인 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ가 payload를 

나타내며 디코딩하면 아래와 같습니다.

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

등록된 Claim은 토큰 정보를 표현하기 위해 이미 정해진 종류의 데이터입니다. 

iss(토큰 발급자), sub(토큰 제목), aud(토큰 대상자), exp(토큰 만료 시간), nbf(토큰 활성 시간), iat(토큰 발급 시간),

jti(토큰 식별자) 등이 있습니다.

 

뒷 부분인 SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c은 Signature를 나타내며 토큰을 인코딩하거나

유효성 검증을 할 때 사용하는 고유한 암호화 코드입니다. 디코딩 하면 아래와 같습니다.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  
) secret base64 encoded

이렇게 만든 토큰을 요청 헤더에 담아서 보냅니다.

 

AccessToken, RefreshToken

인증받은 사용자에게 토큰을 발급하고 서버에 요청을 할 때 헤더에 토큰을 넣어 보냅니다.

그러면 서버는 토큰을 보고 바로 어떤 사용자인지 판단할 수 있습니다. 

따라서 DB에 요청을 보내지 않기 때문에 성능상 이점이 있습니다.   

 

 

하지만 장점만 있는 것은 아닙니다. JWT의 payload는 암호화 되지 않은 사용자의 정보가 담겨있기 때문에

중요한 정보를 담아서는 안됩니다. 또한 JWT가 탈취를 당해 악의적인 요청을 보낼 수도 있습니다. 

이러한 상황을 막기위해 토큰에 만료시간을 정합니다. 만료시간이 길면 탈취의 위험이 있고 

만료시간이 짧으면 사용자가 불편을 겪습니다. 

 

이러한 문제를 해결하기위해 토큰을 AccessToken, RefreshToken 2가지로 구분하였습니다.

사용자가 로그인을 요청하면 AccessToken, RefreshToken 2가지 모두를 발급합니다.

AccessToken은 지금까지 이야기한 방식과 같습니다. 서버에서는 토큰을 저장하지 않고 클라이언트에 저장하는 방식입니다.

RefreshToken은 클라이언트, 서버 모두에 저장합니다.   

 

예를들어 AccessToken의 만료시간은 30분 RefreshToken은 30일이라고 가정하겠습니다.

로그인 후 30분이 지나지 않았다면 이전 그림과 같이 DB에 요청을 하지 않고 사용자 정보를 가져올 수 있습니다.

30분이 지났다면 AccessToken은 만료되었기 때문에 더이상 사용할 수 없습니다.

이때 클라이언트는 RefreshToken을 서버로 보내고 서버에서는 RefreshToken이 유효한지 판단합니다. 

RefreshToken이 유효하다면 AccessToken을 재발급해줍니다.

 

이 경우에는 위의 세션 방식과 같이 DB에 요청을 해야합니다.

이러한 DB 요청이 늘어난다면 RDB가 아닌 Redis와 같은 인메모리 DB를 고려해볼 수 있습니다.

토큰의 저장 장소

한 가지 더 고려할 점이 있는데 JWT의 payload에 사용자의 정보가 그대로 들어있다고 했습니다. 

당연히 비밀번호와 같은 민감한 정보가 있어서는 안되며 payload에 있는 정보도 보안에 대비해야합니다. 

AccessToken, RefreshToken은 각각 어디에 저장하는 것이 좋을까요?

 

우선 AccessToken은 서버에는 없고 클라이언트에만 저장되는 토큰입니다.

클라이언트의 저장 장소로 웹스토리지, 쿠키를 생각해볼 수 있습니다.

쿠키는 로그인이 필요한 요청이 아니더라도 매 요청시마다 자동으로 전송되기 때문에 다른 저장 장소를 고려해보겠습니다.

웹스토리지는 세션 스토리지, 로컬 스토리지로 나뉩니다.

세션 스토리지는 브라우저 별, 도메인 별, 탭 별로 관리되며 탭 삭제시 삭제됩니다.

로컬 스토리지는 브라우저 별, 도메인 별로 관리되며 만료기간이 없고 직접 삭제해야 합니다.

로컬 스토리지에 저장하거나 보안을 더 높일 필요가 있으면 세션 스토리지에 저장할 수 있습니다.

 

RefreshToken은 클라이언트, 서버 모두에 저장되는 토큰입니다.

이전 그림과 같이 서버에 있는 토큰은 RDB나 인메모리 DB에 저장됩니다.

클라이언트는 AccessToken과 같이 웹스토리지, 쿠키를 생각해볼 수 있습니다.

마찬가지로 쿠키에 저장을 한다면 로그인이 필요한 요청이 아니더라도 매 요청시마다 자동으로 전송됩니다.

하지만 RefreshToken은 만료시간이 30일이기 때문에 AccessToken이 탈취당했을 때의 위험보다 더 큽니다.

따라서 이러한 단점에도 불구하고 보안을 높이기 위해 쿠키에 저장하는 것이 더 나은 방법이라고 생각합니다.

웹 스토리지에 토큰을 저장하는 방식보다 쿠키에 저장하는 방식이 HttpOnly, Secure, SameSite와 같은 쿠키속성으로 

보안성을 높일 수 있기 때문입니다.

 

또한 JWT의 payload에 넣을 정보를 AccessToken과 RefreshToken을 다르게 할 수도 있습니다.

AccessToken에는 민감한 정보만 넣지 않고 로그인 유지에 필요한 정보를 넣었다면

RefreshToken에는 사용자를 식별하기 위한 최소한의 정보만 넣는 것을 고려해봐야 합니다.

 

정리

세션 방식보다 토큰 방식이 DB 성능, 확장성에 유리할 수 있습니다.

보안을 높이기 위해 토큰을 2가지 방식으로 나누고 AccessToken은 클라이언트의 세션 스토리지에 

RefreshToken은 서버에는 RDB나 인메모리 DB, 클라이언트에는 쿠키에 HttpOnly, Secure, SameSite와

같은 쿠키 속성을 사용하여 저장할 수 있습니다.

AccessToken, RefreshToken에 각각 어떤 정보를 저장할 지에 대해서도 생각해봐야 합니다.

 

참고한 자료 

 

[Server] JWT(Json Web Token)란?

현대 웹서비스에서는 토큰을 사용하여 사용자들의 인증 작업을 처리하는 것이 가장 좋은 방법이다. 이번에는 토큰 기반의 인증 시스템에서 주로 사용하는 JWT(Json Web Token)에 대해 알아보도록 하

mangkyu.tistory.com

 

[Server] 토큰 기반 인증 VS 서버(세션) 기반 인증

기존의 시스템에서는 서버 기반의 인증방식을 사용하였다. 하지만 시스템의 규모가 커짐에 따라 서버 기반의 인증 방식은 한계점을 보이기 시작하였고, 토큰 기반의 인증 방식이 등장하게 되었

mangkyu.tistory.com