세션 VS. 토큰! JWT가 뭔가요?
📜 라디오 대본
얄코: 여튼 오늘 다룰 JWT는 인증, Authentication보다는
얄코: 인가, Authorization에 연관된 기술이에요.
미토: 어서와이제이션
얄코: 자, 어떤 사이트나 서비스에 사용자가 로그인해있다는 사실을
얄코: 서버가 인지할 수 있도록 하는 방법이 뭐가 있을까요?
미토: 뭐가 있어요?
얄코: 지난화에 네이버로 예시를 들었으니까
얄코: 오늘은 다음카카오 웹사이트로 해볼게요.
얄코: 지금 이 순간에도 수많은 사람들이 다음 사이트에 들어와서
얄코: 뉴스, 블로그, 웹툰 메일 등 서비스들을 이용하고 있을거에요.
얄코: 웹사이트상의 이런 링크 클릭들 하나하나가 다 요청이고
얄코: 다음 사이트의 서버는 동시다발적으로 들어오는
얄코: 무수한 사용자들의 요청에 응답해주고 있는거죠.
얄코: 이 사람들 중 어떤 사람들은 로그인을 한 상태고
얄코: 다른 사람들은 그냥 방문한 상태에요.
얄코: 서버는 각 요청이 들어올 때마다, 이를 보낸 사용자가
얄코: 다음 계정으로 로그인, 인증과정을 거친 상태인지 확인을 해서
얄코: 그에 따라 이메일 접속, 댓글달기 등 로그인이 필요한 기능들에
얄코: 허용을 해줄지 말지를 결정해서 응답해야 해요.
미토: 내가 다음 사이트에서 어디를 돌아다니고 뭘 하든간에
미토: 다음에서 그 모든 활동에 대해 내가 로그인이 됐는지를 확인해야된다.
미토: 그러면은 그냥 내 아이디랑 비번을 크롬갖은데 저장해가지고
미토: 매 요청마다 보내서 로그인을 하면 안 되는거에요?
얄코: 그렇게 하기에는 로그인이란 게 꽤 무거운 작업이에요.
얄코: 일단 데이터베이스 저장된 사용자 계정의 해시값 등을 꺼내온 다음에
얄코: 이것들이 사용자의 암호를 복잡한 알고리즘으로 계산한 값과
얄코: 일치하는지 확인하는 과정 등을 거쳐야 하거든요.
미토: 계산 자체도 빡신데다 데이터베이스에서 뭘 꺼내오는것도
미토: 시간이랑 자원을 많이 잡아먹겠네요. 그럼 요청마다 하는 건 무리지.
얄코: 그리고 매 요청마다 아이디랑 패스워드가 실려서 날아다니면
얄코: 아무래도 보안상 위험하기도 하구요.
미토: 그러겠네요. 그러면은 어떻게 해요?
얄코: 일단 기존의, 전통적으로 많이 사용되어온 '세션' 방식을 설명할게요.
얄코: 사용자가 로그인에 성공하면, 서버는 '세션 표딱지'란 걸 출력해요.
얄코: 영화관 티켓 같은 걸 떠올리시면 돼요.
얄코: 그리고 이걸 쭉 찢어서 반쪽은 사용자의 브라우저로 보내고
얄코: 다른 반쪽은 자기 책상, 메모리에 올려놓는거죠.
얄코: 경우에 따라서는 서랍, 즉 하드디스크에 넣거나
얄코: 창고, 데이터베이스에 넣어놓기도 해요.
미토: 뭘 넣거나 꺼내는 작업이 얼마나 가볍고 빠르냐에 따라서
미토: 메모리는 책상, 하드는 서랍, 데이터베이스는 창고에 비유한거죠?
얄코: 이따 조금 더 자세히 얘기하겠지만, 일단 메모리 즉 책상 위가-
얄코: 다른 둘보다 훨씬 빠른 건 맞아요.
얄코: 여튼 그럼 표 반쪽을 받은 사용자의 브라우저, 크롬이나 엣지같은거죠.
얄코: 브라우저가 이 표를 Session ID란 이름의 쿠키로 저장하고-
얄코: (쿠키란 게 브라우저에 저장되는 정보랬죠). 이 브라우저는 앞으로
얄코: 다음 사이트에 요청을 보낼때마다 이 표딱지를 실어보내요.
미토: 음~ 그러면은 이제 요청에 이 표딱지 반쪽, 세션 아이디란게 실려오면
미토: 서버는 그거를 책상 위, 메모리에서 맞는 짝이 있는지 찾아가지고
미토: 있으면은 어서와이제이션을 해주는 건 거인 거네요.
얄코: 맞아요. 이처럼 이 Session ID를 사용해서 어떤 사용자가
얄코: 서버에 로그인 된 되어있음이 지속되는 이 상태를 '세션'이라고 해요.
미토: 말하자면은 어떤 사용자들이 현재 로그인해있는지를 갖다가
미토: 서버가 표딱지 반쪽들을 어디 둬가지고 기억을 해두고 있는거가
미토: 세션이라는 거네요.
얄코: 그런데 조금만 생각해봐도 이 방식에는 허점이 있죠.
미토: 허점이라... 바로 떠오르는거는 이 책상 위, 메모리에다가
미토: 세션 표딱지 반쪽들을 올려놓으면은 아무래도
미토: 사용자가 동시에 많이 접속을 하면은 메모리가 부족해지겠죠.
미토: 그리고 이 메모리란거는 서버에 문제가 있어서 꺼져버리거나 하면
미토: 죄다 날라가는거 아녜요. 휘발성이라가지구.
얄코: 그렇죠. 에러라는 원숭이가 나타나서 책상을 엎어버리면
얄코: 즉 서버가 재부팅되어야 하는 상황이 오면
얄코: 메모리에 있던 것들은 다 날아가게 되는거죠.
미토: 그러면은 사용자들이 죄다 로그인이 튕겨가지고
미토: 다시 로그인을 해야 되게 되는거네요.
미토: 그럼 이거를 하드, 서랍에 넣어놓으면 되잖아요?
미토: 아, 그러면은 매 요청바다 서랍을 열고 닫고 해야되니까
미토: 아무래도 메모리에서 바로바로 하는 것보단 좀 느리겠구만
얄코: 맞아요. 더 골치아픈 경우는, 서비스가 어느정도 규모가 있어서
얄코: 서버를 여러 대를 두고 사이트를 운영할 때에요.
미토: 서버가 여러 대 있다는 거는 서버들 여러명이가
미토: 각자 책상이랑 서랍을 두고 일을 하고 있고
미토: 손님들 요청이 들어오면은 그 요청들을 갖다가
미토: 그 여러 서버들 사이에 분산을 해서 로드바란싱을 해주는건데
미토: 만약에 로그인은 1번 서버에 연결돼서 하고
미토: 그 다음에 이메일 페이지로 가는 요청은 3번 서버한테 가면은
미토: 3번 직원 책상이나 서랍에는 그 사용자 표딱지 반쪽이 없으니까
미토: 그러면은 세션 유지가 제대로 안 되는거잖아요?
얄코: 그렇죠.
미토: 그렇다고 사용자의 요청이 각자 할당된 서버로만 보내지게 하는것도
미토: 되게 번거롭고 까다로울텐데?
얄코: 맞아요. 다른 방법으로는, 이 표딱지 반쪽을
얄코: 공용 창고 즉 데이터베이스 서버에 넣어두거나
얄코: (대신 이렇게 하면 속도가 많이 느려지겠죠.)
얄코: 더 흔히는, 레디스나 MenCached 같은 메모리형 데이터베이스 서버
얄코: 즉 길다란 공용 책상을 따로 둬서 그 위에다 올려두기도 해요.
얄코: 물론 이 책상이 엎어지는것도 리스크겠죠.
미토: 서버가 복잡한 구성과 환경에서 어떤 상태를 기억해야된다는게
미토: 되게 설계하기가 머리아픈거네요.
얄코: 네. 그래서 그런 부담 없이 이 인가를 구현하기 위해-
얄코: 고안된 게 '토큰 방식'인 JWT에요.
미토: 이게... J S O N, 즈손 웹 토큰이에요?
얄코: 제이슨라고 읽어요. XML처럼 데이터 담을 때 쓰는 그 JSON.
얄코: JSON Web Token이죠. 줄여서 JWT.
미토: 웹 '토큰'. 이 토큰방식은 또 뭐에요?
얄코: JWT를 사용하는 서비스에서는, 이제 사용자가 로그인을 하면
얄코: 역시 토큰이라는 표를 출력해서 건네줍니다.
얄코: 대신 이번에는 찢어서 주지 않고 그냥 줘요.
얄코: 서버가 뭔가를 기억하고 있지 않는다는 이야기죠.
미토: 토큰만 줘버리고 서버는 그냥 잊어버린다구요?
얄코: 네. 이게 어떻게 돌아가는건지 말해줄게요.
얄코: 사용자가 여기서 받는 토큰은 이렇게 생겼어요.
미토: 뭘 이렇게 생겨요, 라디오에서. 읽어줘야 되는데 되게 기네 이거.
미토: eyJhbGciOi 어쩌구 저쩌구
미토: 뭘 인코딩했을때처럼 알파벳이랑 숫자들이 아무렇게나 섞여가지고
미토: 한 백몇십글자가 이어져 있는데, 이게 토큰이에요?
얄코: 네, 인코딩 또는 암호화된 3가지 데이터를 이어붙인거에요.
얄코: 잘 보면 중간에 점, 마침표가 두 군데 들어가 있는게 보일거에요.
미토: 그러네. XXXXXX.YYYYYY.ZZZZZZ 네요.
미토: 이 마침표를 기준으로 세 부분으로 나뉘는거구만.
얄코: 맞아요. 고정댓글에 링크로 달아놓은 [jwt.io](http://jwt.io) 에 접속해보시면
얄코: 예시가 아주 보기 좋게 나와있어요.
얄코: 말씀하셨다시피 마침표로 끊어져서 세 부분으로 나뉘는데
얄코: 각각 header, payload, verify signature로 구분돼요.
얄코: 지금부터 '1번 헤더', '2번 페이로드', '3번 서명'으로 부를게요.
미토: 입으로 설명할려니까 이런게 좀 까다롭네요.
얄코: 이제 하나하나 알아볼게요. 가운데 부분, 2번 페이로드부터 시작합니다.
얄코: 이걸 Base64로 디코딩해보면 JSON형식으로 여러 정보들이 들어있어요.
얄코: 이 토큰을 누가 누구에게 발급했는지, 이 토큰이 언제까지 유효한지
얄코: 그리고 서비스가 사용자에게 이 토큰을 통해 공개하기 원하는 내용
얄코: 이를테면 사용자의 닉네임이나 서비스상의 레벨, 관리자 여부 등을
얄코: 서비스 측에서 원하는대로 담을 수 있죠.
얄코: 이렇게 토큰에 담긴 사용자 정보 등의 데이터를 Claim이라고 해요.
미토: 사용자가 로그인을 하고 나서 받는 토큰에
미토: 이 정보들이 클레임이라는걸로다가 실려온다는거죠.
미토: 이게 그 이후 요청들마다 이번에는 사용자로부터 서버한테 보내지는거고.
미토: 사용자가 받아서 갖고 있는 토큰 자체에 이런 정보들이 들어있으면은
미토: 서버가 요청마다 일일이 데이터베이스에서 뒤져봐야할 것들이 줄겠네요.
미토: 근데 특별한 암호화도 아니고 Base64로 인코딩돼있는거면
미토: 사용자가 자바스크립트로든 뭐로든 다시 디코딩해서 볼 수 있다는거고
미토: 아니, 그러면은 사용자가 이거를 조작해서 악용할 수도 있는거 아녜요.
미토: 토큰의 유효기간을 한 50년 늘려놓는다던가
미토: 관리자 여부 같은게 토큰에 담겨있으면 이걸 true로 바꽈가지고
미토: 관리자 권한으로 인가를 받아버리면 어떻게 해요?
얄코: 그래서 앞부분 1번과 뒷부분 3번 파트가 있는거에요.
얄코: 1번 헤더부터 살펴볼게요. 이걸 디코딩하보면 두가지 정보가 담겨있어요.
얄코: 먼저 type, 토큰의 타입인데 여기에는 언제나 JWT가 들어가요.
얄코: type이 JWT여야 JWT인거죠. 고정값이에요.
얄코: 다른 하나는 alg, 이게 중요해요. 알고리즘의 약자인데
얄코: 여기에는 3번 서명 값을 만드는데 사용될 알고리즘이 지정돼요.
얄코: HS256 등 여러 암호화 방식 중 하나를 지정할 수 있어요.
얄코: 1번 헤더와 2번 페이로드, 그리고 '서버에 감춰놓은 비밀 값' 이 셋을
얄코: 이 암호화 알고리즘에 넣고 돌리면 3번 서명 값이 나오는거에요.
미토: 오~ 이 암호화 알고리즘이라는게 한쪽 방향으로는 계산이 돼도
미토: 반대쪽으로는 안 되는거라가지고, 서버만 알고 있는
미토: 그 비밀 값을 찾아낼 방법이 없는거잖아요.
미토: 토큰들 잔뜩 탈취해서 백날 들여다봐도.
미토: 그리고 글자 하나만 바뀌어도 3번 값이 완~전히 달라지는거라가지고
미토: 2번 페이로드를 수정해서 유효한 3번 서명 값이가 나올라면은
미토: 서버에 숨긴 비밀키를 알고 있어야됭께 이걸 조작을 못하는거겠네요.
얄코: 바로 그거에요. 서버는 요청에 토큰 값이 실려들어오면
얄코: 1, 2번의 값을 '서버의 비밀 키'와 함께 돌려봐서 계산된 결과값이
얄코: 3번 서명 값과 일치하는 결과가 나오는지 확인해요.
얄코: 만약 2번 페이로드의 정보가 서버가 아닌 누군가에 의해
얄코: 조금이라도 수정되었다면 당연히 안 맞겠죠.
얄코: 정보를 조작한 사용자이거나 해커인걸로 간주돼서 거부돼요.
얄코: 3번 서명 값과 계산값이 일치하고, 유효기간도 지나지 않았다면
얄코: 그 사용자는 로그인 된 회원으로서 인가를 받는거에요.
미토: 옹~ 그럼 이제 서버는 사용자들의 상태를 어디다가 따로
미토: 기억을 해 둘 필요가 없이, 이 비밀 값만 손에 쥐고 있으면은
미토: 요청들 들어올대마다 토큰 삑삑 스캔해가지고
미토: 사용자들을 걸러낼 수 있겠네요.
얄코: 그렇죠. 이처럼 시간에 따라 바뀌는 어떤 상태값을 안 갖는 걸
얄코: stateless하다고 해요. 세션은 반대로 stateful이죠.
미토: 그러면은 인제 다 세션 안 쓰고 JWT 쓰면 되는거에요?
미토: JWT가 우월해요? JWT가 짱짱맨이에요?
얄코: 안타깝게도, 세션을 대체하기에는 JWT에 큰 결점이 있어요.
얄코: 세션처럼 stateful해서, 모든 사용자들의 상태를 기억하고 있다는 건
얄코: 구현하기 부담되고 고려사항도 많지만, 이게 되기만 하면
얄코: 기억하는 대상의 상태들을 언제든 제어할 수 있다는 의미거든요.
얄코: 예를 들어서 한 기기에서만 로그인 가능한 서비스를 만들려는 경우
얄코: PC에서 로그인한 상태의 어떤 사용자가 핸드폰에서 또 로그인하면
얄코: PC에서는 로그아웃되도록 기존 세션을 종료할 수 있는거죠.
미토: 오호라, 세션 방식에서는 책상에 올려둔 기존 표딱지를 버리면 되는데
미토: JWT에서는 그런게 불가능하겠네요.
미토: 이미 줘버린 토큰을 뺏을 수도 없고 그 토큰의 발급 내역이나 정보를
미토: 서버가 어디 기록해서 추적하고 있는것도 아니니까.
미토: 내가 쥐고 있을 필요가 없어서 편하기는 한데, 그래서 통제는 못한다.
미토: 이거는 좀 센데. 그렇다고 어디 상태를 기억해둘려고 하면은
미토: 세션이랑 똑같애져버리니까 의미가 없는 거 아녜요.
얄코: 맞아요. 더 심각한 경우로, 어떤 토큰이 악당에게 탈취당한 경우
얄코: 이 악당이 가져가버린 토큰을 무효화할 방법도 없는거죠.
얄코: 때문에 실 서비스중에 JWT만으로 인가를 구현하는 곳은
얄코: 생각보다 그렇게 많지는 않아요.
미토: 아까 들었을때는 되게 기발하고 신박하고 되게 좀 좋고 그랬는데
미토: 이런 문제들땜세 다는 안쓰인다니까 좀 아쉽고 그러네요.
얄코: 물론 이런 점을 나름대로 보완하기 위해 쓰는 방법들이 있는데
얄코: 만료시간을 가깝게 잡아서 토큰의 수명을 아주 짧게 주는거에요.
미토: 그러면은 몇 분 이따가 또 로그인 하고 그래야되잖아요.
얄코: 그래서, 로그인을 하고 나면 토큰을 두 개 주는거에요.
얄코: 수명이 몇 시간이나 몇 분 이하로 짧은 access 토큰이랑
얄코: 꽤 길게, 보통 2주 정도로 잡혀 있는 refresh 토큰인데요.
얄코: 지금부터 말씀드릴 건 이 둘을 구현하는 여러 방법들 중 하나에요.
얄코: access 토큰과 refresh 토큰을 발급하고 클라이언트에게 보내고 나서
얄코: refresh 토큰은, 상응값을 데이터베이스에도 저장합니다.
얄코: 손님은 access 토큰의 수명이 다하면 refresh 토큰을 보내요.
얄코: 서버는 그걸 데이터베이스에 저장된 값과 대조해보고
얄코: 맞다면 새 access 토큰을 발급해주는거죠.
얄코: 이제 이 refresh 토큰만 안전하게 관리된다면 이게 유효할동안은
얄코: access 토큰이 만료될 때마다 다시 로그인을 할 필요 없이
얄코: 새로 발급을 받을 수 있는거죠.
미토: 매번 인가를 받을 때 쓰는 수명 짧은 토큰이 엑세스 토큰이고
미토: 엑세스 토큰을 재발급받을 때 쓰는거가 리프레시 토큰이다.
미토: 그렇게 하면은 중간에 엑세스 토큰이 탈취당해도
미토: 오래 쓰지는 못하겠네요.
미토: 누구를 강제 로그아웃시킬라면은 리프레시 토큰을 갖다가
미토: DB에서 지워버려가지고 토큰갱신이 안 되게 하면 되는거고.
미토: 하지만은 그렇게 해도 짧게나마 엑세스 토큰이 살아있는 동안은
미토: 이걸 바로 차단할 방법은 없겠죠.
미토: 그냥 토큰 하나로만 쓰는것보다는 훨씬 낫지만은
미토: 이것도 아주 속 시원하게 빈틈없는 해결책은 아니네요.
미토: 효자손에 비닐장갑 끼워서 등 긁는 느낌이네.
얄코: 네, 완벽한 방법은 아니죠. 이런점이 현재 JWT의 한계에요.
얄코: 때문에 아무리 JWT가 구현하기 편리하고 좋더라도
얄코: 이를 적용하기에 내 서비스가 적합한지 충분히 고려해야 해요.
얄코: 물론 위 문제가 큰 이슈가 안 되는 종류의 서비스에서는
얄코: 정말 편리하고 경제적인 인가 방식이 되겠죠.
얄코: 오늘 이처럼 세션 방식과 JWT를 사용한 토큰 방식을 알아봤어요.
얄코: 이후 얄코 라디오에서 다뤄주었으면 하는 주제들 있으면
얄코: 댓글로 남겨주시면 감사하겠습니다.
얄코: 이상 얄코와 미토였습니다. 즐코하세요!
유튜브에서 영상 보기