JWT - 기본 개념

인증이 필요한 이유

앱, 웹 혹은 서버 개발을 하면서 꼭 사용하게 되는 인증은 아주 중요하다. 인증은 프론트엔드 관점에서 봤을 때 사용자의 로그인, 회원가입과 같이 사용자의 도입부분을 가리킨다. 반면 서버사이드 관점에서 봤을 때는 모든 API 요청에 대해 사용자를 확인하는 작업이다. 만일 설계를 잘못한다면, 회원들의 정보 유출이 일어날 수도 있다.

사용자 A와 B가 앱을 사용한다고 가정하자. 두 사용자는 기본적으로 정보가 다르고 보유하고 있는 컨텐츠도 다르다. 따라서 서버에서는 A, B가 요청을 보냈을 때 누구의 요청인지를 정확히 알아야 한다. 만일 그렇지 못한다면, 자신의 정보가 타인에게 유출되는 최악의 상황이 발생한다. 그렇기에 앱(프론트 엔드)에서는 자신이 누구인지를 알만한 단서를 서버에 보내야 하며, 서버는 그 단서를 파악해 해당 요청자에 맞는 데이터만 뿌려주게 된다.

HTTP 요청에 대해

현재 모바일이나 웹 서비스에서 가장 많이 쓰이는 통신 방식은 HTTP 통신이다. HTTP 통신은 응답을 완료한 후 연결이 끊기게 된다. 즉, 응답을 송수신할 때만 연결을 하고 그 외에는 연결을 끊은 상태를 유지한다. 이로 인해 과거에 대한 정보를 전혀 담지 않는다. 그때그때 정보만을 송수신 하는 것이다. 이 말은 지금 보낼 HTTP 요청은 지난 번에 내 정보를 담아 보냈던 HTTP 요청과는 전혀 관계가 없다는 말이다. 따라서 각각의 HTTP 요청에는 주체가 누구인지에 대한 정보가 대부분 필수적이다. (가끔 주체가 누구인지 파악할 필요가 없는 경우도 있다.)

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

인증 방식

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

계정 정보를 요청 헤더에 담아 보내는 방식이 가장 보안이 낮은 방식이다. 위에서 언급한 HTTP 요청에 인증할 수단인 비밀번호를 넣는 것이다. 정말 최악의 인증방식이다. 데이터를 요청할 때마다 사용자의 프라이빗한 정보(id, password 등등)를 계속해서 보낸다는 건 상당히 보안에 안 좋다. 보통 앱에서는 서버로 HTTP 요청을 할 때 따로 암호화되지 않는다. 따라서 해커가 마음만 먹으면 HTTP 요청을 가로채서(intercept) 사용자의 계정정보를 알 수 있다. 본 방식은 절대로 실제 서비스에서는 쓰이지 않는다. HTTP 요청을 암호화해서 보안을 높이는 방식으로 HTTPS가 있지만 계정 정보를 요청 헤더에 넣는 방식은 사용하지 않는다.

장점

  • 인증을 테스트할 때 빠르게 시도해볼 수 있다.

단점

  • 보안에 매우 취약하다.
  • 서버에서는 요청이 올 때마다 id, password를 통해 유저가 맞는지 인증해야 한다. 그래서 비효율적이다.

위의 1번 방식을 통해 계정 정보(id, password 등등)를 매번 요청에 넣어서 보내기엔 보안에 너무 취약하다. 따라서 그 다음으로 나온 인증 방식이 Session / Cookie 이다.

순서는 요약하면 다음과 같다.

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

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

** 세션 ID를 쿠키라고 봐도 동일하다. 쿠키가 사용자 개념에서 더 큰 범주이지만, 세션 ID를 쿠키로 저장하는 셈이다.

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

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

장점

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

단점

  1. 만약 A사용자의 HTTP 요청을 B사용자(해커)가 가로챘다면 그 안에 들어있는 쿠키도 충분히 훔칠 수 있다. 그리고 B 사용자는 그 훔친 쿠키를 이용해 HTTP 요청을 보내면 서버의 세션저장소에서는 A 사용자로 오인해 B 사용자한테 A 사용자의 정보를 전부 노출시키게 되는 것이다. (이를 세션 하이재킹 공격이라고 한다.)

    → 해결책 : HTTPS를 사용해 요청 자체를 탈취해도 안의 정보를 읽기 힘들게 한다. / 세션에 유효시간을 넣어준다.

  2. 서버에서 세션 저장소를 사용하므로, 서버에서 추가적인 저장공간을 필요로 하게 되고 자연스럽게 부하도 높아진다.

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

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

jwo.io 사이트 캡쳐

인증 순서 전에 간단하게 JWT에 대해서 알아보자. https://jwo.io 를 들어가면 암호화된 토큰을 볼 수 있다.

토큰을 만들기 위해서는 크게 3가지, Header, Payload, Verify Signature가 필요하다.

  • Header : 위 3가지 정보를 암호화할 방식(alg), 타입(type) 등이 들어간다.
  • Payload : 서버에서 보낼 데이터가 들어간다. 일반적으로 유저의 고유 ID값, 유효기간이 들어간다.
  • Verify Signature : Base64 방식으로 인코딩한 Header와 Payload와 SECRET KEY를 더한 후 서명된다.

토큰이 Encoded Hedaer + "." + Encoded Payload + "." + Verify Signature 형태로 발급된다.

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

하지만 Verify Signature는 SECRET KEY를 알지 못하면 복호화할 수 없다.

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

이제부터는 JWT가 어떻게 인증에 사용되는지 알아보자.

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

세션/쿠키 방식과 가장 큰 차이점은 세션/쿠키는 세션 저장소에 유저의 정보를 넣는 반면, JWT는 토큰 안에 유저의 정보들을 넣는다는 점이다. 물론 클라이언트 입장에서는 HTTP 헤더에 세션 ID나 토큰을 실어서 보내준다는 점에서는 동일하나, JWT 토큰은 서버 측에서는 인증을 위해 암호화를 하는 방식이고, 세션/쿠키 방식은 별도의 저장소를 이용하냐는 차이가 발생한다.

장점

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

단점

여기까지의 글만 봤을 때는 JWT가 세션/쿠키 방식보다 더 효율적으로 보인다. 하지만 JWT도 단점들이 존재한다.

  1. 이미 발급된 JWT에 대해서는 돌이킬 수 없다. 세션/쿠키의 경우 만일 쿠키가 악의적으로 이용된다면, 해당하는 세션을 지워버리면 된다. 하지만 JWT는 한 번 발급되면 유효기간이 완료될 때까지는 계속 사용이 가능하다. 따라서 악의적인 사용자는 유효기간이 지나기 전까지 신나게 정보를 털어갈 수 있다.

    → 해결책 : 기존의 Access Token의 유효기간을 짧게 하고, Refresh Token이라는 새로운 토큰을 발급하면 된다. 그렇게 되면 Access Token을 탈취당해도 상대적으로 피해를 줄일 수 있다.

  2. Payload 정보가 제한적이다. 위에서 언급했다시피 Payload는 따로 암호화되지 않기 때문에 디코딩하면 누구나 정보를 확인할 수 있다. (세션/쿠키 방식에서는 유저의 정보가 전부 서버의 저장소에 안전하게 보관된다.) 따라서 유저의 중요한 정보들은 Payload에 넣을 수 없다.
  3. 세션/쿠키 방식에 비해 JWT의 길이가 길다. 따라서 인증이 필요한 요청이 많아질수록 서버의 자원낭비가 발생하게 된다.

Refresh Token이란 ?

Access Token(JWT)를 통한 인증 방식의 문제는 만일 제 3자에게 탈취당할 경우 보안에 취약하다는 점이다. 유효기간이 짧은 Token의 경우 그만큼 사용자는 로그인을 자주 해서 새롭게 Token을 발급받아야 하므로 불편하다. 그러나 유효기간을 늘리자니, 토큰을 탈취당했을 때 보안에 더 취약해지게 된다. 왜냐하면 JWT는 한 번 발급되면 유효기간이 완료될 때까지는 계속 사용이 가능하기 때문이다.

이 때, “그러면 유효기간을 짧게 하면서 좋은 방법이 있지는 않을까?”라는 질문의 답이 바로 “Refresh Token”이다.

Refresh Token은 Access Token과 똑같은 형태의 JWT이다. 처음에 로그인을 완료했을 때 Access Token과 동시에 발급되는 Refresh Token은 긴 유효기간을 가지면서, Access Token이 만료됐을 때 새로 발급해주는 열쇠가 된다. (여기서 만료라는 개념은 그냥 유효기간을 지났다는 의미이다.)

예를 들어보자. Refresh Token의 유효기간은 2주, Access Token의 유효기간은 1시간이라 하자. 사용자는 API 요청을 신나게 하다가 1시간이 지나게 되면 , 가지고 있는 Access Token은 만료 된다. 그러면 Refresh Token의 유효기간 전까지는 Access Token을 새롭게 발급 받을 수 있다.

** Access Token은 탈취당하면 정보가 유출되는 건 동일하다. 다만 짧은 유효기간 안에만 사용이 가능하기에 더 안전하다는 의미이다.

** Refresh Token의 유효기간이 만료됐다면, 사용자는 새로 로그인 해야 한다. Refresh Token도 탈취될 가능성이 있기 때문에 적절한 유효기간 설정이 필요해 보인다. (보통 2주로 많이 잡는다.)

Refresh Token을 사용해야 하는 이유

[질문 & 답변] Refresh Token을 사용해야 하는 이유

Access Token + Refresh Token 인증 과정

  1. 사용자가 id, password를 통해 로그인한다.
  2. 서버에서는 회원 DB에서 값을 비교한다. (보통 password는 일반적으로 암호화해서 들어간다.)

3~4. 로그인이 완료되면 Access Token, Refresh Token을 발급한다. 이때 일반적으로 회원 DB에 Refresh Token을 저장해둔다.

  1. 사용자는 Refresh Token은 안전한 저장소에 저장 후, Access Token을 헤더에 실어 요청을 보낸다.

6~7. Access Token을 검증하여 이에 맞는 데이터를 보낸다.

  1. 일정 시간이 지나 Access Token이 만료됐다고 가정하자.

  2. 사용자는 이전과 동일하게 Access Token을 헤더에 실어 요청을 보낸다.

10~11. 서버는 Access Token이 만료됨을 확인하고 권한없음을 신호로 보낸다.

** Access Token 만료가 될 때마다 계속 과정 9~11을 거칠 필요는 없다. 사용자(프론트엔드)에서 Access Token의 Payload를 통해 유효기간을 알 수 있다. 따라서 프론트엔드 단에서 API 요청 전에 토큰이 만료됐다면 바로 재발급 요청을 할 수도 있다.

  1. 사용자는 Refresh Token과 Access Token을 함께 서버로 보낸다.

  2. 서버는 받은 Access Token이 조작되지 않았는지 확인한 후, Refresh Token과 사용자의 DB에 저장되어 있던 Refresh Token을 비교한다. Token이 동일하고 유효기간도 지나지 않았다면 새로운 Access Token을 발급해준다.

  3. 서버는 새로운 Access Token을 헤더에 실어 다시 API요청을 진행한다.

Refresh Token이 들어가면서 과정이 좀 복잡해졌다. 하지만 Access Token의 약점을 보완해주기 때문에 보안이 중요한 프로젝트에서는 사용하기를 권장한다.

장점

  • 기존의 Access Token만 있을 때보다 안전하다.

단점

  • 검증 프로세스가 길기 때문에 구현하기가 조금 더 힘들어졌다. (프론트엔드, 서버 모두)
  • Access Token이 만료될 때마다 새롭게 발급하는 과정에서 생기는 HTTP 요청 횟수가 많아진다. 이는 서버 자원의 낭비로 귀결된다.

References

쉽게 알아보는 서버 인증 1편(세션/쿠키 , JWT)

쉽게 알아보는 서버 인증 2편(Access Token + Refresh Token)

JWT(JSON Web Token)에 대해서… :: Outsider’s Dev Story