웹 서비스는 기본적으로 HTTP 통신 위에서 동작한다. 이 때 HTTP의 큰 특징 중 하나는 바로 stateless하다는 것이다. 간단히 말하면 서버가 클라이언트의 예전 상태를 보존하지 않는다는 뜻으로 비유하자면 클라이언트가 바로 직전에 한 말조차도 기억하지 못한다는 것! 예를 들면..

 

1번째 교신)

클라 : 제주도 가는 비행기 얼마에요?

서버 : 편도 7만원이요

 

2번째 교신)

클라 : 4장 주세요

서버 : ? 뭘요?

 

이런 느낌이다. 좀 더 자세한 얘기는 예전에 쓴 글 참조.

https://jofestudio.tistory.com/57

 

HTTP의 특징(stateless 등..)

1. 클라이언트 - 서버 구조 : HTTP는 클라이언트가 request를 보내면 서버가 response를 보내는 구조이다. 즉 클라이언트와 서버가 개념적으로 분리된 형태이며, 클라이언트로부터 request가 안오면 서버

jofestudio.tistory.com

 

 

암튼 이런 식으로 각 통신의 상태를 저장하지 않기 때문에, 인증 / 인가와 관련한 문제점이 생긴다. 로그인 이후 마이페이지같은 걸 요청할 때, 서버가 내 쪽의 상태를 모르니 매번 새 페이지를 요청할때마다 로그인을 해야 한다. 

 

이런 번거로움을 넘어서 비효율적인 일을 느낌있게 처리해야 한다. 기존에 이미 로그인한 사용자에 대한 정보를 저장해야 할 필요가 있는 것이다. 이를 위한 대표적인 방식들로 세션(Session)과 토큰(Token)을 기반으로 하는 인증방식들이 있다. 둘 다 유저가 로그인 시도 시 일치하는 유저 정보를 찾았다면 인증(Authentication) 완료의 표시로 일종의 확인증을 클라이언트에게 끊어주고, 클라이언트는 이후 로그인한 유저 정보가 필요한 곳에는 발급받은 확인증을 서버에게 건네어 인가(Authorization)를 받는다는 공통점이 있다. 그러나 둘은 차이점이 있고 서로간의 장단점이 있다. 이 둘에 대해 좀 더 뜯어보자.

 

 

세션 (Session)

일정 시간 동안 같은 사용자(정확히는 브라우저)로부터 들어오는 요구들을 하나의 상태로 보고 그 상태를 일정하게 유지시키는 기술을 말한다. 쉽게 말해 방문자가 웹서버에 접속해있는 상태를 하나의 단위로 보는 것. 컨셉은 다음과 같다.

 

  1. 유저가 로그인을 시도하면, 서버 쪽에서 DB를 뒤져서 일치하는 유저 정보를 찾는다.
  2. 찾았다면 그 클라이언트에 대한 세션id(일종의 확인증)을 발급해주고, 서버 쪽에서 갖는 세션 스토리지(Session Storage)에도 해당 정보를 저장한다. (세션id가 일종의 식별key)
  3. 클라이언트는 발급받은 세션id를 쿠키에 저장한다. (2번의 response로부터 set-cookie가 옴)
  4. 이후 클라이언트는 request를 보낼 때 쿠키를 헤더에 넣어 함께 전송(세션id가 들어있음)
  5. 서버는 클라이언트로부터 받은 세션id와 자신의 세션 스토리지에 있는 세션id를 대조해 인증 상태를 판단하고, 인가여부를 내린다.

 

서버 쪽에서 클라이언트의 세션정보를 본인의 메모리나 디스크..뭐 이런 곳에 저장하는 방식으로, 결국은 클라이언트의 상태를 계속해서 서버가 갖고 있고 이를 서비스에 이용하는 stateful방식이다.

 

※ 장점

서버가 클라이언트의 상태를 유지하고 있으니 로그인 여부 확인이 매우 용이하다. 맘만 먹으면 강제 로그아웃 등의 제재를 가하는 것도 쉽다. 

 

※ 단점

서버가 각 클라이언트들의 상태를 모두 갖고있는 셈이니, 당연히 메모리나 디스크 등에 부하가 걸리기 쉽다. 가장 치명적인 단점은 MSA(Micro Service Architecture)문제점이다.  사용자가 많아지면 서버를 여러 개로 늘려 확장한 다음, 로드밸런서(Load Balancer)를 둬서 각 클라이언트들의 보내는 request들을 처리할 서버를 이곳 저곳으로 전달해야 한다. 이 때 내가 로그인을 요청했던 서버와 이후 요청을 보낸 서버가 달라지면, 그 서버에는 내가 아까 로그인을 할 때 저장된 세션 정보가 없다는 문제가 발생하게 되는 것. 이렇게 되면 세션 정보가 없는 다른 서버에 접속할 때마다 계속 로그인을 해야 하는 참사가 생겨버린다. 이를 위해 각 서버들의 세션 스토리지를 동기화해주는 세션매니저(Session Manager)를 두는 방법 등이 있긴 하지만 추가적인 cost가 발생하게 된다..

 

 

토큰(Token)

세션 기반 인증 빙삭이 세션 정보(즉 인증 정보)를 서버에 저장하고 사용하는 식이라면, 토큰 기반 인증 방식은 서버가 아닌 클라이언트가 인증 정보를 직접 들고 있는 방식이다. 대표적으로 JWT(Json Web Token)방식이 가장 유명하다.

 

저작권 문제시 삭제하겠습니다..

 

컨셉은 다음과 같다

 

  1. 유저가 로그인을 시도하면, DB를 뒤져서 일치하는 유저 정보를 찾는다
  2. 찾았다면, 서버 쪽에서 가지고 있는 Secret key로 토큰을 발급한다.
  3. 클라이언트는 발급된 토큰을 저장하고, 이후 요청부터 토큰을 HTTP header에 실어 함께 보낸다.
  4. 서버는 단순히 전달받은 토큰이 유효한지 검증하고, 유효하면 인가해준다.

 

※ 장점

서버 쪽에서 저장하는 게 아니니 서버 쪽의 메모리가 확보된다. 또한 서버를 여러 개로 확장한 경우에도 모든 서버컴퓨터가 유저 정보를 기억하게 할 필요가 없다. 즉 확장성 쪽에서 엄청난 장점을 가진다. 

 

※ 단점

세션 인증 방식의 경우 세션 id만 실어서 보낸다. 반면 토큰의 경우는 사용자 인증 정보 뿐만 아니라 토큰의 발급 시각, 토큰의 id등의 정보도 가지고 있다. 즉 세션id보다 토큰의 사이즈가 더 크기 때문에 훨씬 더 많은 네트워크 트래픽이 유발된다.

뿐만 아니라 세션 인증 방식은 인증 정보를 서버에서 관리하는 반면 토큰은 클라이언트가 모든 인증 정보를 들고 있는 셈이라 보안 측면에서 좀 아쉽다. 세션id는 해커들에게 탈취돼도 서버측에서 해당 세션을 무효 처리하면 되는 반면 토큰은 해커들도 탈취한 다음 지들 맘대로 사용할 수 있다. 

본 포스트는 아래 영상을 요약한 글로 원작자의 허락을 받고 올립니다. 저작권 문제시 본 글은 삭제조치하겠습니다

https://www.youtube.com/watch?v=1QiOXWEbqYQ&t=1s 

 


로그인? 아이디랑 비번 DB에 갖고있다가 로그인할 때는 그게 DB에 있는지 조회하면 될 줄 알았지만 그건 너무 날먹이었고~ㅋㅋㅋ 보안문제땜에 당연히 그렇게 안 한다고 한다.

 

로그인 기능을 만들기도 쉽지 않지만, 더 까다로운 건 로그인 상태를 유지하는 것! 지메일에 로그인했는데 메일 열 때마다 뭐 할때마다 로그인할 수는 없다. 내가 로그인하면 로그인했다는 걸 서버가 계속 알고 있어야 한다. 인증(Authentication)과 인가(Authorization)의 차이가 중요한 것.

 

JWT는 인가 즉 Authorization에 관련된 기술로, 어떤 사이트나 서비스에 사용자가 로그인했다가 서버에 인지시키는 것과 관련된다.

 

인가란 분야에서 전통적으로 많이 사용된 건 세션(session)이다. 사용자가 로그인에 성공하면 서버는 세션표딱지를 출력하는데 이걸 쭉 찢어서 반쪽은 브라우저라 보내고 반쪽은 자기가 갖는다. 브라우저는 이를 session ID라는 이름의 쿠키로 저장한다! 브라우저는 그럼 로그인된 사이트에 요청을 보낼따마다 이 표딱지를 실어보내고, 서버는 요청에 표딱지반쪽이(즉 session ID가) 서버는 자신이 가진 표딱지들 중 브라우저로부터 받은 반쪽짜리와 맞는 표딱지가 있는지 보고 ‘인가’한다. 이렇게 session ID를 사용해 서버에 로그인되어있음이 지속되는 상태를 ‘세션’이라고 표현한다. 그러나 이는 서버쪽 메모리가 날아가거나(재부팅 등으로) 너무 많은 유저들이 로그인하면 여러 반쪽표딱지들을 갖고 있어서 메모리가 부족해지는 현상이 생기기도 한다. 또한 여러 서버를 운영할 경우 로드밸런싱 문제로 로그인은 A 서버에서 하고(반쪽표딱지가 A 서버 메모리에 저장됨) 인가는 B 서버에서 진행될 수도 있는데, B 서버에는 A 서버에서 로그인한 반쪽표딱지가 없으니 에러가 생기기도 한다..

 

그래서 이런 부담없이 고안된 인가 방식이 토큰 방식인 JWT다. JSON WEB TOKEN의 줄임말이다. 토큰방식은 로그인하면 토큰이랑 딱지를 주는데, 세션방식과는 달리 반으로 찢지 않고 그대로 준다. 이는 서버가 뭔가를 기억하지 않는다는 얘기다! 토큰은 알파벳과 숫자들이 마구잡이로 섞인 문자열로 인코딩 또는 암호화된 3가지 데이터를 이어붙인 거다. XXXXXX.YYYYYY.ZZZZZZ같은 형태이며 마침표를 기준으로 세 가지 부분(헤더, 페이로드, 서명)으로 구분한다.

Payload

디코딩하면 JSON형식으로 여러 정보들이 들어있다. 토큰을 누가 누구에게 발급했는지, 언제까지 유효한지 등..토큰에 담긴 사용자 정보 등의 데이터를 Claim이라고 한다. 이를 통해 로그인 이후 요청들마다 사용자로부터 서버로 이 Claim들이 전해지는데, 사용자가 발급받아 갖고 있는 토큰 자체에 이런 정보들이 들어있으면 서버는 요청마다 하나하나 DB에서 뒤질 필요가 없으니 더 편하다. 근데 디코딩이 매우 쉬워서 누군가 수정할 오류가 있다! 근데 조금이라도 바뀌면 금방 알아챌 수 있다. 어떻게 하는지는 다음에 계속 서술

Header

디코딩하면 type(언제나 JWT가 들어감)과 alg(알고리즘)정보가 나온다. 여기는 ‘3번 서명값’을 만드는데 사용될 알고리즘이 지정된다(ex : HS256)

즉, Header와 Payload 그리고 서버에 감춰둔 비밀 값 이라는 3가지 요소를 암호화 알고리즘에 넣고 돌리면 3번 서명값이 나오는 거다. 암호화 알고리즘은 한쪽 방향으로만 계산이 돼서 토큰을 탈취해서 염병을 해도 서버가 감춰둔 비밀 값을 알아낼 수 없다.

여기서 주목. 서버는 요청에 토큰 값이 실려오면 Header와 Payload를 서버가 감춘 비밀값과 함께 알고리즘을 돌려서 계산된 값이 3번 서명값과 같다면, 그리고 유효기간이 지나지 않았다면 인가처리를 해준다. 이것이 JWT다. 참고로 Payload가 조금이라도 바뀌면 알고리즘 돌려서 나오는 값이 완전히 달라져서 3번 서명값과 같아지지 않는다. 이를 통해 payload변조를 알아챌 수 있다.

이를 통해, 서버는 사용자들의 상태를 어디다가 기록해둘 필요 없이 자신의 비밀 값만 가지고 있다면 요청들 들어올 때마다 헤비한 작업을 할 필요가 없다. 이처럼 시간에 따라 바뀌는 어떠한 상태값을 갖지 않는 걸 stateless하다고 표현한다. 세션은 반대로 stateful하다고 표현할 수 있다

그러나 JWT의 결점

stateful방식 즉 모든 사용자들의 상태를 기억하는 건 구현이 어렵고 고려사항도 많지만 구현만 되면 기억하는 대상의 상태들을 언제든 제어가능하다. 예를 들어 한 기기에서만 로그인가능한 서비스를 만든다고 하자. PC에서 로그인한 상태의 어떤 유저가 핸드폰으로 또 로그인하려하면 PC에서는 로그아웃되도록 만들 수도 있다는 얘기! JWT는 이런게 불가능하다. 이미 줘버린 토큰을 뺏을 수도 없고 토큰의 발급 내역(payload에 저장되는 정보)를 서버가 따로 추적할 수도 없으니..

즉 JWT는 내가 쥐고 있을 필요가 없어 편하긴 한데 그만큼 통제를 못 하는 방식이다.

또한 토큰이 만약 탈취된다면, 이 토큰을 무효화할 방법이 없다는 것도 큰 단점이다ㅋㅋ. 때문에 토큰을 두 개 발급하는 방법이 쓰이곤 한다. 완벽한 해결책은 아님. 이런 문제들이 큰 이슈가 안되는 서비스들에 한해서만 JWT만을 쓰는 인가방식을 쓰는 게 좋다.

+ Recent posts