JWT가 무엇인가? 에 들어가기에 앞서 로그인에 대해 고찰해 보고자 한다. 개발자가 아닌 일반 사람들은 로그인 기능이 구현하기 굉장히 쉬운(?) 기능이라고 생각할 수 도 있다.
나 또한 배우기 전에는 그냥 db에 id랑 pw 저장하고 로그인 요청을 보낼때 그걸 대조해서 맞으면 로그인시키고 아니면 안시키고 이러면 되는거 아닌가? 했었다.
하지만 그렇게되면 보안에 심각한 이슈를 초래할 수 있다. 만약 해커에게 db가 뚫린다면 회원들의 id와 비밀번호가 전부 다 유출 되게 되버리는 셈이니까...
그리고 로그인이 제대로 구현이 잘 됬다 쳐도, 구현하기 만만찮게 까다로운게 로그인 상태가 계속 유지가 되도록 하는 것이다.
유저가 메일을 보기 위해 로그인을 하고 받은 메일함을 열때 또 로그인을 하고 메일을 보내려고 할 때마다 로그인을 해야한다면 너무 불편하지 않겠는가?
개발자가 아닌 사람이 보기엔 "그게 어려워?" 라고 말할 수 있지만 유저가 로그인을 한번 하면 그 유저가 계속 로그인이 되있다는 것을 서버가 알고 있게 해야 한다는건데 그게 쉬운일이 아니라는걸 나도 개발자 공부를 시작하고 나서야 알았다.
인증과 인가에 대해 짚고 가보자
인증(Authentication)은 쉽게 말해 로그인이라고 생각하면 된다. 내가 이 사이트에 가입된 화원임을, 즉 권한이 있는 사용자임을 id 와 pw 를 통해 인증을 받는걸 말한다.
인가(Authorization)는 이렇게 한번 인증을 받은 사용자가 이후 사이트가 제공하는 서비스를 사용 할때, 즉 내가 지마켓에 로그인을 하고 나서 내가 그동한 장바구니에 넣어둔 상품들을 보거나 구입한 상품에 대한 후기글을 작성하거나 등등 내 계정으로만 할 수 있는 활동을 시도할 때 지마켓 서버에서 내가 로그인이 되어 있음을 확인하고 허가를 해주는 것을 예로 들 수 있다. 간단하게 정리하면 로그인이 유지되는 상태에서 일어나는 일이라고 보면된다.
이제 JWT가 뭔지 알아보려한다.
JWT란 무엇인가?
JSON Web Tokens 약자로, 전자 서명 된 URL-safe (URL로 이용할 수있는 문자 만 구성된)의 JSON Token이다.
JWT를 사용하는 서비스에서는 사용자가 로그인을 하면 토큰을 만들어서 건내준다. 토큰을 건네주고 서버는 더이상 해당 토큰에 대한 내용을 저장하지 않는다.
JWT는 세 파트로 나누어지며, 각 파트는 점로 구분하여 xxxxx.yyyyy.zzzzz 이런식으로 표현되고. 순서대로 헤더 (Header), 페이로드 (Payload), 서명 (Sinature)로 구성한다.
인코딩 또는 암호화된 3가지 데이터를 이어 붙인 건데 마침표를 기준으로 3부분으로 나뉜다.
먼저 2번째 부분 페이로드를 base64 로 디코딩해보면 Json 형식으로 여러 정보들이 들어있다. 이 토큰을 누가 누구에게 발급했는지, 이 토큰이 언제까지 유효한지 그리고 서비스가 사용자에게 이 토큰을 통해 공개하기 원하는 내용 즉, 사용자의 닉네임이나 서비스 상의 레벨, 관리자 여부 등등을 서비스 측에서 원하는대로 담을 수 있다. 이렇게 토큰에 담긴 사용자 정보 등의 데이터를 Claim 이라고 한다.
단순히 base64 디코딩할수 있으면 사용자가 이를 악용할 가능성이 있으므로 헤더와 서명이 필요하다.
헤더를 디코딩해보면 두가지 정보가 담겨있는데 먼저 type은 고정값으로 토큰의 타입인데 여기엔 언제나 JWT가 들어간다. tpye이 JWT어야지만 이게 JWT이다.
다른 하나는 alg, 알고리즘의 약자인데 여기엔 3번째 서명 값을 만드는데 사용될 알고리즘이 지정된다. 캡처한 예시처럼 HS256등 여러 암호화 방식 중 하나를 지정할 수 있다.
1번 헤더와 2번 페이로드, 그리고 서버에 감춰놓는 비밀 값 이 셋을 이 암호화 알고리즘에 넣고 돌리면 3번 서명값이 나오는 구조이다.
서버는 요청값에 토큰 값이 실려 들어오면 1,2번의 값을 '서버의 비밀 키' 와 함께 돌려봐서 계산된 결과값이 3번 서명값고 일치라는 결과가 나오는지 확인해서 일치해야지만 로그인을 통과 시켜준다.
만약 2번 페이로드의 정보가 서버가 아닌 누군가에 의해 조금이라도 바뀌었다면 당연히 일치하지 않고 엑세스가 거부된다.
그렇다면 JWT가 Session보다 휠씬 우월한가?
안타깝게도 세션을 대체하기엔 JWT에게 큰 결점이 있다.
세션처럼 stateful해서, 모든 사용자들의 상태를 기억하고 있다는 건 구현하기 부담되고 고려사항도 많지만, 이게 구현만 된다면 기억하는 대상의 상태들을 언제든 제어할 수 있기 때문이다.
예를 들어 한 기기에서만 로그인 가능한 서비스를 만드려는 경우 PC에서 로그인한 상태의 어떤 사용자가 핸드폰에서 또 로그인하면 PC에서는 로그아웃되도록 기존 세션을 종료 할 수 있는것
세션 방식에서는 저장해놓은 세션을 버려버리면 되는데 JWT에서는 이미 줘버린 토큰을 다시 뺏을 수도 없고 그 토큰의 발급 내역이나 정보를 서버가 어디 기록해서 추적하고 있는것도 아니니까...
서버가 뭔갈 저장하고 꺼낼일이 없어서 편하긴 하지만 그래서 통제는 못하는게 바로 JWT이다.
API란 무엇인가?
Application Programming Interface의 약자로
하나의 프로그램에서 다른 프로그램으로 데이터를 주고 받기 위한 방법을 말합니다.
정의만 들으면 아 그렇구나? 하겠는데
그래서 그 방법 이 뭔데요?
식당의 메뉴판이라고 생각을 해 봅시다.
우리는 식당을 가면 메뉴판을 보고 주문을 합니다.
손님이 만약 가서 메뉴판에 적힌대로 주문하지 않고 지 맘데로 주문 한다면? 당연히 제대로 주문이 되질 않겠죠?
메뉴판을 보고 정확하게 "1번 메뉴 주세요!" 혹은 "저는 피자 주세요!" 라고 해야지 원하는 음식을 받아 볼 수 있을겁니다.
여기서 바로 메뉴판이 API가 되겠습니다
식당주인과 손님이 음식을 주고 받기 위한 방법인 거죠
실제 왭서비스를 예로 들어볼까요?
웹툰을 유저에게 제공하는 웹서버가 있다고 칩시다.
여기서 무턱대고 유저가 웹툰에 있지도 않은
"드래곤볼 보여주세얌" 이러면 당연히 서버는 보여주지 않습니다.
웹서버에서는 정확한 메뉴판 즉 API를 정해줘야 하고 우리 서비스에서는 이러이러한 웹툰들을 제공한다 라고 알려줘야합니다. 그래야 서비스가 가능하겠죠?
그러니까 웹툰서비스 API 는 웝툰서버와 유저가 웹툰을 주고받기 위한 방법 즉 서비스를 하기위해서 서버측이 미리 만들어 놓은 메뉴판인겁니다.
그러나 이렇게만 설명하면 이 방법 이라는게 아직 조금 추상적이라 이해가 힘들 수 있습니다.
그냥 쉽게 말해보면 코드입니다.
코드를 짜는거에요
아래는 이번에 제가 프로젝트에서 사용한 코드입니다.
db에서 리뷰글을 가지고와서 보여주는 GET방식의 API입니다.
@app.route("/review/show", methods=["GET"]) # 여기서 '/review/show' 이 url로 GET 요청을 보내면
def get_review(): # 요기 아래 코드를 실행해 주세요~ 하는코드
review_list = list(db.class_reviews.find({}, {'_id': False}))
return jsonify({'reviews': review_list})
이렇게 어떤 유저가 서버가 정해놓은 url로 요청을 했을때 서비스를 유저가 볼 수 있도록 미리 짜 놓는 겁니다.
API를 작성할 땐 작성하는 법이 정해져 있습니다.
웹은 REST API 라는 원칙에 따라 작성하면 좋은데 이해가 쉽고 관리가 쉬운 API 작성법이라고 보면 됩니다. RESTful API란?
API 는 public 즉 공개되있는 API만 있는게 아닙니다.
사내에서만 쓰는 private API 라던가 partner API 라고 해서 미리 정해둔 유저만 쓸수 있게 만드는 API도 있습니다.