2016년 8월 24일 수요일

JWT 토큰은 어디에 저장하는게 좋을까?

이전에 JWT 토큰에 대한 글을 쓴 적이 있는데 그것의 연장선 상에 있는 이야기이다. JWT 를 구현하는데에 있어서 토큰을 어디에 저장해야 하는가는 한번 생각해 볼만한 문제이다. 토큰 인증이란 결국 사용자가 인증받은 토큰을 들고 다니면서 서버에 자원을 요청시에 함께 전달해야 하는 것인데 그러기 위해서는 어딘가에는 토큰을 클라이언트가 저장을 해야 하는 것이다.

웹 어플리케이션을 제작중이라면 생각해볼 수 있는 2가지 방법이 있다.

  1. HTML5 web storage (Local Storage, Session Storage)
  2. Cookies
그럼 이 두 가지를 하나씩 살펴 보면서 비교해보자.

1. HTML5 web storage

web storage 는 HTML5 부터 지원되는 기술로 클라이언트(브라우저)에 데이터를 저장할 수 있는 방법 중 하나이다. 이전에는 클라이언트에 데이터를 저장하기 위해서는 쿠키를 사용했었다. web storage 는 쿠키에 비해서 보안 좀 더 뛰어나며 웹사이트 성능에 영향 없이 더 많은 데이터(최소 5MB) 를 저장할 수 있다는 장점이 있다. web storage 는 각 도메인과 프로토콜 단위로 저장되어 진다. web storage 는 Local Storage 와 Session Storage 가 있는데 둘의 차이점은 Session Storage 는 브라우저 창이 꺼지면 데이터가 삭제 된다는 점이다.


(크롬 개발자 도구를 사용하면 집접 이 값을 추가/수정/삭제 가 가능하다.)

다시 토큰이야기로 돌아와보자. 처음 사용자 인증을 통해서 받게된 토큰을 바로 이제 여기에 저장할 수 있는 것이다. 저장은 자바스크립트 localStorage 객체를 통해 쉽게할 수 있다.

// response.token 에 api 를 통해 받은 토큰 값이 정해져있다고 가정.

var token = response.token; 
localStorage.setItem("token",token);

(크롬 개발자 도구를 통해 Local Storage 를 확인해보면 저장 되었음을 확인할 수 있다.)

이렇게 토큰을 저장해두면 나중에 서버에 HTTP 로 요청할때에 함께 보내면 된다. 보통은 HTTP 이용시 Authorization 헤더에 Bearer 스키마로 함께 보낸다. 서버측에서는 이 내용을 파싱해서 토큰을 확인하는 식이다.

이렇게 web storage 에 저장하는 방법은 나쁘지 않다. 자바스크립트로 값을 쉽게 저장하고 가져올 수 있기 때문에 편리한 이점이 있다. 토큰을 디코딩 해서 페이로드에 담긴 정보를 활용하기도 쉽다. 그런데 이러한 점은 사실 보안 측면에서는 좋지 않다. 자바스크립트도 제어 가능하다는 것은 곧 XSS(cross-site scripting) 공격에 취약할 수 있음을 의미한다. XSS 는 쉽게 말하면 해커가 자바스크립트 코드를 웹페이지에 심어 사용자의 정보를 탈취하는 종류의 공격이다. 일반적으로 웹 어플리케이션들은 사용자로부터 데이터를 입력받게 되는데 이 데이터에 해커가 자바스크립트 코드를 심어 놓을 수 있는 것이다. 쉽게 생각해 게시판에 글을 쓴다고 생각해보자. 글 내용에 해커가 자바스크립트 코드를 심어 놓고 사용자들이 이 글을 보면서 동시에 자바스크립트 코드가 실행되어 해커가 원하는 것을 얻을 수 있게 된다. 물론 이런 공격은 많이 알려져 있기 때문에 대부분에 이와 같은 입력은 사전에 필터링을 통해 차단한다. 그럼에도 불구하고 이와 같은 공격에는 여러가지 우회 방법이 존재할 수 있기 때문에 튼튼하게 방어해 놓지 않는다면 위험이 늘 존재하는 것이다. XSS 공격에 대해 자세히 알고 싶다면 이곳을 참조하자.


2. Cookies

다음으로 생각해볼 수 있는 것은 쿠키다. 쿠키는 예전부터 많이 써오던 기술이다. 간단히 설명하면 사용자 인증을 하게 되면 서버측에서는 이를 HTTP Set-Cookie 헤더를 통해서 토큰을 보낸다. 브라우저는 이를 통해서 쿠키를 생성하고 토큰을 저장한다. 이후에 해당 API 에 요청을 하게 될때에는 브라우저는 자동으로 이 쿠키를 실어서 보낸다. 간단하다. 

쿠키를 서버에 전달한다는 방식에 있어 브라우저가 자동으로 전달해주기 때문에 구현하는 입장에서는 더 편리한 점도 있다. 그런데 web storage 와 마찬가지로 쿠키 또한 자바스크립트를 통해서 조작이 가능한데 web storage 가 쿠키보다 더 좋은 점이 많은데 차이점은 무엇일까? 쿠키는 자바스크립트로 조작이 가능하지만 옵션 설정을 하면 이를 막을 수 있다는 점이 큰 차이점이다. 그 첫번째 옵션은 HttpOnly 이다. 쿠키 생성시에 이 옵션을 주게 되면 쿠키는 자바스크립트로 접근이 불가능하다. 오로지 HTTP 통신을 통해서만 쿠키가 전송된다. 그렇기 때문에 web storage 에서 발생할 수 있었던 XSS 공격에 대해 방어할 수 있다. 여기서 그치지 않고 Secure 옵션을 주게 되면 쿠키는 HTTPS 통신으로만 전송되기 때문에 보안 수준을 한 단계 더 높여줄 수 있다.

여기까지 보면 확실히 web storage 보다 cookie 를 사용하는 것이 보안적인 측면으로 뛰어나 보인다. 그러나 이 방식 또한 완벽하지는 않다. 이방식은 CSRF(cross-site request forgery) 라고 불리우는 또 다른 공격에 취약할 수 있다. CSRF 공격은 쿠키 전송방식의 취약점을 활용하는 방식이다. 쉽게 설명하면 A.com 에서 생성한 쿠키는 B.com 에서는 열어볼 수 없는 것이다. 아주 당연한 이야기이다. 위에서 설명했듯이 A.com 으로 HTTP 요청을 하면 브라우저는 알아서 쿠키를 헤더에 담아서 보낸다. HttpOnly 옵션을 준 경우 해커는 쿠키를 자바스크립트를 통해서 얻을 수 없고 결국 HTTP 헤더를 통해서 탈취를 해야 한다. 그런데 쿠키가 헤더에 포함되어 전송되는 것은 해당 도메인에 한해서만인데 어떻게 이걸 중간에 가로챌 수 있단 말인가? 힌트는 쿠키가 전송될 때에는 목적지의 도메인에 따라서 포함 여부가 결정되지 출발지가 어디인지에 대해서는 신경쓰지 않는 다는 것에 있다. 복잡한데 예를 들어 생각해보자.

해커는 cometome.hack 이라는 사이트를 제작했다. 그리고 a.com 을 사용하는 사용자에게 이메일로 그럴듯한 메일을 보내서 클릭하면 cometome.hack 해당 사이트에는 접속하자마자 해당 유저의 브라우저가 a.com 에 http 요청을 보내도록 한다. 이를테면 HTML 에 아래와 같은 코드를 삽입해 놓는 것이다.

<img src="http://a.com/api/changeMyName/idiot">

낚시에 걸려든 사용자는 이 사이트에 접속되자 마자 자기도 모르는 사이 a.com 의 닉네임을 변경하는 api 를 호출하고 있는 것이다. 그런데 이미 a.com 에 접속해서 얻은 쿠키가 유효하기 때문에 이 요청은 이 쿠키를 담아서 보내질 것이고 API 는 해당 쿠키의 토큰을 통해 인증 여부를 확인하고 요청한 작업을 처리할 것이다.

이렇다면  결국 Cookie 를 사용 하는 것도 문제인 것인가? 그렇지 않다. 여기에는 이를 방어할 수 있는 방법들이 있다. API에 대한 HTTP 요청을 특정 함수를 통해서만 이루어지도록 하는 것이다. 그래서 이 함수를 통한 요청이 아닐 경우에는 인증을 거부하는 것이다. (토큰에 특정 해쉬 값을 추가해서 발송하는 식으로) 이런 방법 외에도  HTTP 헤더를 분석해서 막는 방법도 있다. HTTP 헤더에 있는 Referer 와 Origin 값을 통해서 엉뚱한 곳에서 요청이 날아 왔다면 이를 거부할 수 있다.

결론
위에서 설명한 2가지 방식 중에서 무엇을 선택해야 하는가의 기준이 되는 것은 결국 보안이다. 우리가 논의하고 있는 것은 결국 인증을 위한 것인데 사용자 인증에 있어서 가장 중요한 것은 보안이기 때문이다. 2가지 방법 모두 저마다의 취약점은 존재하기 마련이다. 그런데 개인적으로 생각하기에는 쿠키에 저장하는 것이 보안이 더 높다고 생각한다. XSS 공격은 다방면에서 스크립트를 차단해야 하기 때문에 자칫 실수로 공격에 노출될 수 있는데 이에 반해 CSRF 공격은 위에 소개된 방법을 통해 보다 더 쉽고 단단하게 방어할 수 있다고 생각되기 때문이다. 물론 어느 방법도 완벽하다고 이야기할 수는 없다. 보안이라는 것은 늘 취약점을 찾아 들어오는것 이기에 프로그래머가 이를 의식하지 않고 개발한다면 어떠한 방식이든 뚫릴 수 있기 때문이다.



댓글 9개 :

  1. 잘읽었습나다. 좋은 정보 감사합니다

    답글삭제
  2. 엄청난 글 감사합니다 :)

    답글삭제
  3. 정리가 잘 된 좋은 내용 감사합니다. 잘 읽고 갑니다.

    답글삭제
  4. 좋은 글 감사합니다.
    httponly를 적용하게되면 jwt의 payload는 클라이언트도 볼 수 없을텐데 jwt의 장점중에 하나가 사라지는 건 아닐까요
    의견 부탁드릴께요 :)

    답글삭제
    답글
    1. 굳이 클라이언트 쪽에서 payload를 볼 필요가 있을까요?

      삭제