세션 / 쿠키 , JWT 인증

📖 오늘 PG앱 개발 미팅에서 나왔던 주제인 인증 방법에 관해 공부 및 정리하는 글을 남겨두려한다…

📚6.3일 업데이트 : 현재 앱 스펙이 파이썬에 flask 웹프레임워크를 사용중인데, flask의 경우 flask 서버내에서 임의로 세션을 생성해서 헤더에 쿠키로 실어보낸다. 어떤 방식으로 생성하는건지, 알고리즘은 어떤걸 사용하는건지 ,, 컨트롤은 어떻게 하는건지는 더 찾아봐야할 듯 하다.

✅ 인증이 필요한 이유

인증은 프론트엔드 관점에서는 로그인, 회원가입 정도의 도입 부분이겠지만, 서버 사이드에서는 모든 API 요청에 대해 사용자를 확인하는 작업이라고 볼 수 있다.

예를 들어 사용자 A와 사용자 B가 앱을 사용한다고 가정해보자. 두 사용자는 기본적으로 정보가 다르고 보유하고 있는 컨텐츠도 다르다. 따라서 서버에서 A,B 가 요청을 보냈을때 알맞는 데이터를 주기 위해서 누구의 요청인지 정확히 알아야 한다.
만약 그렇지 못하면… 자신의 정보가 타인에게 유출되는 최악의 상황(🚫)이 발생할 것이다.

👏 현재 많이 쓰이는 통신 방식

현재 모바일이나 웹 서비스에서 많이 쓰이는 통신 방식은 http 통신이다. http 통신은 응답 이후 연결이 끊기게 되며 과거에 대한 정보를 전혀 담지 않는다. 이 말은 지금 보낼 http 요청은 지난 번에 내 정보를 담아 보냈던 http 요청과 전혀 관계가 없다는 말이 된다. 따라서 각각의 http 요청에는 주체가 누구인지에 대한 정보가 필수적이다.

서버에 요청을 보내는 작업은 http 메세지를 보내는 것이다. http 메세지의 구조는 다음과 같다. 일반적으로 헤더와 바디 두가지로 구성되며, 공백은 헤더와 바디를 구분짓는 역할을 한다. 여기서 헤더에는 기본적으로 요청에 대한 정보가 들어가고, 바디에는 서버로 보내야할 데이터가 들어가게 된다. 보통 모바일/웹 서비스 인증은 http 메세지의 헤더에 인증 수단을 넣어 요청을 보내게 된다.

🔑 인증 방식

1. 계정 정보를 요청 헤더에 넣는 방식 (🚫)

가장 보안이 낮은 방식으로 계정정보를 요청에 담아 보내는 방식이다. 위에서 언급한 http 요청에 인증할 수단에 비밀번호를 넣는다.

데이터를 요청할때마다 사용자의 프라이빗한 정보를 계속해서 보낸다는 건 상당히 보안에 좋지 않다. 보통 앱에서 서버로 http 요청을 할 때 따로 암호화되지 않는다. 따라서 해커가 마음만 먹으면 요청을 가로채서 사용자의 계정정보를 알 수 있는 것이다. 본 방식은 절대로 실제 서비스에서는 쓰이지 않는다. (http 요청을 암호화해서 보안을 높이는 방식인 https가 있지만…그래도 안쓴다)

또 서버는 요청이 들어올때마다 id,pw를 통해 유저가 맞는지 인증해야하므로 상당히 비효율적이기도 하다.

2. Session / Cookie 방식 (✅)

위의 계정정보를 매번 요청에 넣어서 보내기엔 보안에 너무 취약하다. 그래서 나온 방식이 Session / Cookie 이다.

인증 순서

  1. 사용자가 로그인을 한다.
  2. 서버에서는 계정정보를 읽어 사용자를 확인한 후, 사용자의 고유한 ID값을 부여하여 세션 저장소에 저장한 후, 이와 연결되는 세션 ID를 발행한다.
  3. 사용자는 서버에서 해당 세션ID를 받아 쿠키에 저장을 한 후, 인증이 필요한 요청마다 쿠키를 헤더에 실어 보낸다.
  4. 서버에서는 쿠키를 받아 세션 저장소에서 대조를 한 후 대응되는 정보를 가져온다.
  5. 인증이 완료되고 서버는 사용자에 맞는 데이터를 response로 보내준다.

세션 쿠키 방식의 인증은 기본적으로 세션 저장소를 필요로 한다. (Redis 를 많이 사용한다고 한다.) 세션 저장소는 로그인을 했을 때 사용자의 정보를 저장하고 열쇠가 되는 세션 ID 값을 만든다. 그리고 http 헤더에 실어 사용자에게 돌려보낸다. 그러면 사용자는 쿠키로 보관하고 있다 인증이 필요한 요청에 쿠키(세션ID)를 넣어 보낸다. 웹 서버에서는 세션 저장소에서 쿠키(세션ID) 를 받고 저장되어 있는 정보와 매칭시켜 인증을 완료한다.

✏️ 세션 ID를 쿠키라고 봐도 무방하다. 쿠키가 사용자 개념에선 더 큰 범주이며, 세션 ID를 쿠키로 저장하는 셈이다.

✏️ 세션과 쿠키는 엄연히 다르다. 세션은 서버에서 가지고 있는 정보이며, 쿠키는 사용자에게 발급된 세션을 열기 위한 열쇠(SESSION_ID)를 의미한다. 쿠키만으로 인증을 사용한다는 말은 서버의 자원을 사용하지 않는다는 것이며, 이는 즉 클라이언트가 인증 정보를 책임진다는 말이다. 그렇게되면 http요청을 탈취당할 경우 정보가 노출된다. 따라서 보안과는 상관없는 단순한 장바구니나 자동로그인 설정 같은 경우에는 유용하게 쓰인다.

결과적으로 인증의 책임을 서버가 지게하기 위해 세션을 사용한다. 사용자(클라이언트)는 쿠키를 이용하고, 서버에서는 쿠키를 받아 세션의 정보를 접근하는 방식으로 인증을 한다.

😀 장점

  1. 세션/쿠키 방식은 기본적으로 쿠키를 매개로 인증을 거친다. 여기서 쿠키는 세션 저장소에 담긴 유저 정보를 얻기위한 열쇠라고 보면 된다. 따라서 쿠키가 담긴 http 요청이 도중에 노출되더라도 쿠키 자체(SESSION_ID)는 유의미한 값을 가지고 있지 않다. 중요한 정보는 서버에 위치하고 있다.
  2. A사용자는 1번, B사용자는 2번 이런식으로 고유의 ID 값을 발급받게 된다. 그렇게 되면 서버에서는 쿠키 값을 받았을 때 일일이 회원정보를 확인할 필요 없이 바로 어떤 회원인지를 확인할 수 있어 서버 자원에 접근하기 용이하다.

️☹️ 단점

  1. 장점 1에서 쿠키를 탈취당하더라도 안전할 수 있다고 언급했지만, 문제가 하나 있다. 만일 A 사용자의 HTTP 요청을 B 사용자(해커)가 가로챘다면 그 안에 들어있는 쿠키도 충분히 훔칠 수 있다. 그리고 B 사용자는 그 훔친 쿠키를 이용해 HTTP 요청을 보내면 서버의 세션저장소에서는 A 사용자로 오인해 정보를 잘못 뿌려주게 된다.(세션 하이재킹) 
    => https를 사용해 요청 자체를 탈취해도 안의 정보를 읽기 힘들게하거나, 세션에 유효기간을 넣어준다.
  2. 서버에서 세션 저장소를 사용하기 때문에 서버에서 추가적인 저장공간을 필요로 하게되고 자연스럽게 부하도 높아진다. 
  3. 중앙 세션 관리 시스템에 장애가 일어나면, 시스템 전체에 문제가 생긴다.
  4. 메모리에 세션 정보가 들어가있다면, 메모리가 많이 사용될 수 있다.

3. 토큰 기반 인증 방식 JWT (✅)

JWT는 세션/쿠키와 함께 모바일과 웹의 인증을 책임지는 대표주자라고 볼 수 있다. JWT(Json Web Token) 은 인증에 필요한 정보들을 암호화시킨 토큰을 뜻한다.위의 세션/쿠키 방식과 유사하게 사용자는 Access Token(JWT 토큰)을 http 헤더에 실어 서버로 보내게 된다.

jwt.io ㅅ사ㅇ사이트

인증순서 전에 간단하게 JWT에 대해 알아보자.

기본 구성 : xxxxx.yyyyy.zzzzz

 https://jwt.io 를 들어가보면 암호화된 토큰을 볼 수 있다.  토큰을 만들기 위해서는 크게 3가지 Header,Payload, Verify Signature가 필요하다. 

  • Header : JWT인 토큰의 유형이나 HMAC SHA256 또는 RSA와 같이 사용되는 해시 알고리즘이 무엇으로 사용했는지 등 정보가 담긴다. base64url로 인코딩 되어있다.
  • Payload : 서버에서 보낼 데이터가 들어간다. 일반적으로 유저의 고유 ID값, 유효기간이 들어간다. base64url로 인코딩되어 있다.
  • Verify Signature :  header에서 지정한 알고리즘과 secret키, 서명으로 payload와 header를 담는다.

최종적인 결과 : Encoded Header + “.” + Encoded Payload + “.” + Verify Signature

Header, Payload는 인코딩될 뿐(16진수로 변경), 따로 암호화되지 않는다. 따라서 JWT 토큰에서 Header, Payload는 누구나 디코딩하여 확인할 수 있다. 여기서 누구나 디코딩할 수 있다는 말은 Payload에는 유저의 중요한 정보(비밀번호)가 들어가면 쉽게 노출될 수 있다는 말이 된다.

하지만 Verify Signature는 secret key를 알지 못하면 복호화할 수 없다.  A 사용자가 토큰을 조작하여 B 사용자의 데이터를 훔쳐보고 싶다고 가정해보자. 

payload에 있던 A의 ID를 B의 ID로 바꿔서 다시 인코딩한 후 토큰을 서버로 보냈다. 그러면 서버는 처음에 암호화된 Verify Signature를 검사한다. 여기서 Payload는 B사용자의 정보가 들어가 있으나 Verify Signature는 A의 Payload를 기반으로 암호화되었기 때문에 유효하지 않는 토큰으로 간주하게 된다. 여기서 A사용자는 SECRET KEY를 알지 못하는 이상 토큰을 조작할 수 없다는 걸 확인할 수 있다.

인증 순서

  1. 사용자가 로그인을 한다.
  2. 서버에서는 계정정보를 읽어 사용자를 확인한 후, 사용자의 고유한 ID값을 부여한 후에 기타정보와 함께 payload에 넣는다.
  3. JWT 토큰의 유효기간을 설정한다.
  4. 암호화할 secret key를 이용해 Access token를 발급한다.
  5. 사용자는 Access token을 받아 저장한 후, 인증이 필요한 요청마다 토큰을 헤더에 실어 보낸다.
  6. 서버에서는 해당 토큰의 verify signature 를 secret key로 복호화한 후, 조작여부, 유효기간을 확인한다.
  7. 검증이 완료되면, payload를 디코딩하여 사용자의 ID에 맞는 데이터를 가져온다.

❗️JWT의 수명을 짦게하고 정기적으로 재발급을 요구하면 원치않는 클라이언트를 빨리 막을 수 있다.

😀 장점

  1. 간편하다. 세션/쿠키는 별도의 저장소 관리가 필요하지만, JWT는 발급한 후 검증만 하면 되기때문에 추가 저장소가 필요없다. 즉 stateless한 서버를 만드는 입장에서는 큰 강점이다. 여기서 stateless는 어떠한 별도의 저장소도 사용하지 않는, 즉 상태를 저장하지 않는 것을 의미한다. 이는 서버를 확장하거나 유지 보수하는데 유리하다
  2. 확장성이 뛰어나다. 토큰 기반으로 하는 다른 인증 시스템에 접근이 가능하다. 예를 들어 Facebook 로그인, Google 로그인 등은 모두 토큰을 기반으로 인증한다. 이에 선택적으로 이름이나 이메일 등을 받을 수 있는 권한을 받을 수 있다

️☹️ 단점

  1. 이미 발급된 JWT에 대해서 돌이킬 수 없다. 세션 / 쿠키의 경우 만일 쿠키가 악의적으로 이용된다면, 해당하는 세션을 지워버리면 된다. 하지만 JWT는 한번 발급되면 유효기간이 완료될 때까지는 계속 사용이 가능하다. 따라서 악의적인 사용자는 유효기간이 지나기 전까지 계속 정보 탈취가 가능하다.
    = > 기존의 Access Token의 유효기간을 짧게하고 Refresh Token이라는 새로 운 토큰을 발급한다. 그렇게되면 Access Token을 탈취당해도 상대적으로 피해를 줄일 수 있다.
  2. Payload 정보가 제한적이다. 위에서 언급했다시피 Payload는 따로 암호화되지 않기 때문에 디코딩하면 누구나 정보를 확인할 수 있다. (세션/쿠키 방식에서는 유저의 정보가 전부 서버의 저장소에 보관된다). 따라서 유저의 중요한 정보들은 Payload에 넣지않는게 좋다.
  3. JWT의 길이는 세션/쿠키 방식에 비해 길다. 따라서 인증이 필요한 요청이 많을수록 서버의 자원낭비가 발생한다.

생각 정리

오늘 PG앱 (플러그인) 개발 미팅을 진행하면서, 여러 논쟁(?)이 있었는데.. 사실 해당 앱은 JWT 인증 방식을 채용해도 될 것 같다. payload에 실어보낼 데이터가 크지않을뿐더러, stateless한 서버에 가깝기때문이다

사실 쿠키 세션 방식이던 JWT 인증방식이던… 빨리 보안검수 과정 통과해서 앱 릴리즈가 되었으면 좋겠다…. ㅠ_ㅠ

처음 겪는 과정이라 더 오래걸리는거겠지만, 뭐든 참 쉬운건 없다. 아직 갈 길이 멀구나