내가 이 사이트에 가입된 회원임을, 즉 특정 서비스에 일정 권한이 주어진 사용자임을 아이디와 패스워드 등을 통해서 인증 받는 것. => 로그인
한 번 인증을 받은 사용자가 이후 서비스의 여러 기능들 사용할 때 로그인 되어있음을 알아보고 허가를 해주는 것.
로그인이 유지되는 상태에서 일어나는 일.
어떤 사이트나 서비스에 사용자가 로그인해있다는 사실을 서버가 인지할 수 있도록 하는 방법에는 무엇이 있을까?
서버는 각 요청이 들어올 때마다, 이를 보낸 사용자가 로그인, 인증상태를 거친 것인지 확인 후 로그인이 필요한 기능들에 허용을 해줄지 말지를 결정해서 응답해야 함.
사용자가 로그인에 성공하면, 서버는 '세션 표딱지'란 걸 출력. ( 영화관 티켓 이라고 생각 )
그리고 이걸 찢어서 반쪽은 사용자의 브라우저로 보내고 다른 반쪽은 메모리에 올려둠 ( 하드디스크, 데이터베이스 )
무엇을 넣거나 꺼내는 작업이 얼마나 가볍고 빠르냐에 따라 메모리 (책상), 하드디스크 (서랍), 데이터베이스 (창고)
브라우저(크롬, 엣지) 가 이 표를 Session ID란 이란의 쿠키로 저장하고 이 브라우저는 앞으로 다음 사이트의 요청을 보낼때마다 이 표딱지를 실어서 보내게 됨.
이제 요청에 이 표딱지 반쪽, Session ID 란게 실려오면 서버는 그것을 메모리에서 맞는 짝이 있는지 찾아가지고 있으면 Authorization
이처럼 이 Session ID를 사용해서 어떤 사용자가 서버에 로그인 되어있음이 지속되는 이 상태를 '세션' 이라고 함.
=> 허점이 있음
=> 그렇다면, 하드디스크에 넣어두면 안될까?
=> 그렇다면, 데이터베이스에 넣어둔다면?
속도가 많이 느림.
부담 없이 이 Authorization(인가) 를 구현하기 위해 고안된 것이 '토큰 방식' JWT
JWT를 사용하는 서비스에서는, 사용자가 로그인을 하면 역시 토큰이라는 표를 출력해서 건네줌.
대신 이번에는 찢어서 주지 않고 그냥 줌.
=> 서버가 뭔가를 기억하고 있지 않는다는 이야기.
토큰만 줘버리고 서버는 잊어버림.
토큰은 인코딩 또는 암호화된 3가지 데이터를 이어붙인것. (XXXXXXXX.YYYYYYY.ZZZZZZZZ)
각각 header 헤더, payload 페이로드, verify signature 서명 으로 구분됨.
페이로드를 Base64로 디코딩해보면 JSON형식으로 여러 정보들이 들어있음.
=> 그런데 특별한 암호화도 아니고 Base64로 단순히 인코딩되어있는것이라면 보안상 문제가 없을까?
이 문제때문에 앞 부분의 헤더와 뒷 부분의 서명이 존재.
헤더를 디코딩 해보면 2가지 정보가 담겨있음.
1. type : 토큰의 타입인데 여기에는 언제나 JWT가 들어감.
2. alg : 알고리즘의 약자. 여기에는 3번 서명 값을 만드는데 사용될 알고리즘이 지정됨. HS256 등 여러 암호화 방식 중 하나를 지정할 수 있음.
헤더와 페이로드 그리고 '서버에 감춰놓은 비밀 값' 이 셋을 이 암호화 알고리즘에 넣고 돌리면 서명 값이 나옴.
암호화 알고리즘이라는게 한쪽 방향으로는 계산이 되어도 반대쪽으로는 안되므로, 서버만 알고 있는 그 비밀 값을 찾아낼 방법이 없음.
토큰을 탈취해서 들여다보아도, 그리고 글자 하나만 바뀌어도 서명 값이 완전히 달라지게되므로 페이로드를 수정해서 유효한 서명 값이 나오려면 서버에 숨긴 비밀키를 알아야하기 때문에 조작이 불가능.
서버는 요청에 토큰 값이 실려들어오면 헤더, 페이로드 값을 서버의 비밀 키 와 함께 돌려봐서 계산된 결과값이 서명값과 일치하는 결과가 나오는지 확인.
만약 페이로드의 정보가 서버가 아닌 누군가에 의해 조금이라도 수정되었다면 당연히 일치하지 않음.
=> 정보를 조작한 사용자이거나 해커인 것으로 간주되어서 거부됨.
서명 값과 계산값이 일치하고, 유효기간도 지나지 않았다면 그 사용자는 로그인 된 회원으로서 인가를 받음.
서버는 사용자들의 상태를 어디에다 따로 기억을 해둘 필요없이 이 비밀 값만 손에 쥐고 있으면 요청들이 들어올때마다 토큰으로 사용자들을 걸러낼 수 있음
=> 이처럼 시간에 따라 바뀌는 어떠한 상태값을 안 갖는 걸 stateless 세션은 반대로 stateful
안타깝게도, 세션을 대체하기에는 JWT에 큰 결점이 있음.
세션처럼 stateful해서, 모든 사용자들의 상태를 기억하고 있다는 건 구현하기 부담되고 고려사항도 많지만, 이게 되기만 하면 기억하는 대상의 상태들을 언제든 제어할 수 있다는 의미.
예를 들어 한 기기에서만 로그인 가능한 서비스를 만드려는 경우
PC에서 로그인한 상태의 어떤 사용자가 핸드폰에서 또 로그인하면 PC에서는 로그아웃되도록 기존 세션을 종료할 수 있음.
쉽게 말하면 세션방식에서는 책상위에 올려준 표딱지를 버리면 되는데 JWT에서는 그런것이 불가능함.
이미 줘버린 토큰을 뺏을 수도 없고 그 토큰의 발급 내역이나 정보를 서버가 어디 기록해서 추적하고있는것도 아니기 때문.
=> 내가 쥐고 있을 필요가 없어서 편하기는 한데, 통제는 불가능하게됨.
더 심각한 경우로 어떤 토큰이 악당에게 탈취당한 경우 이 악당이 가져가버린 토큰을 무효화할 방법도 없음.
=> 실 서비스중에 JWT만으로 인가를 구현하는 곳은 생각보다 많지 않음.
=> 이러한 점을 나름대로 보완하기 위해 쓰는 방법에는 만료시간을 가깝게 잡아서 토큰의 수명을 아주 짧게 설정하는 것.
=> 그러면 로그인을 계속해야되는가? No! 로그인을 하고나면 토큰을 2개를 줌
access token과 refresh token 을 발급하고 클라이언트에게 보내고 나서 refresh 토큰은 상응값을 데이터베이스에도 저장을 함.
손님은 access token의 수명이 다하면 refresh 토큰을 보냄.
서버는 그것을 데이터베이스에 저장된 값과 대조해보고 맞다면 새로운 access token을 발급.
이제 이 refresh token 만 안전하게 관리된다면 이게 유효할 동안은 access 토큰이 만료될 때마다 다시 로그인을 할 필요 없이 새로 발급을 받을 수 있음.
중간에 엑세스 토큰이 탈취당해도 오래쓰지는 못함. 누구를 강제 로그아웃 시키려면 리프레쉬 토큰을 DB에서 지워버려서 토큰 갱신이 안되게 하면됨.
하지만, 이렇게 하더라도 짧게 나마 access token이 살아있는 동안은 바로 차단할 방법이 없음.
=> JWT의 한계, 아무리 JWT가 구현하기 편리하고 좋더라도 이를 적용하기에 내 서비스가 적합한지 충분히 고려해야 함.