[study] JWT 토큰

유재민·2023년 2월 12일
1

# JWT 토큰(JSON Web Token)

# JWT 토큰이란?

JSON Web Token으로 로그인과 같은 인증(유저 비밀번호 확인)/인가(로그인 유저가 요청하는 request를 처리할 수 있는 지 권한 확인)에서 사용한다. 로그인을 처리하는 방식으로 세션 기반 인증과 토큰 기반 인증이 있는데 토큰 기반 인증에서 사용한다. 세션 기반 인증은 서버의 메모리, 데이터베이스와 같은 서버의 자원들을 사용해서 사용자의 정보를 유지시키는 방식(서버에 저장)이다. 토큰 기반 인증은 사용자가 로그인을 하면 서버에서 발행해주는 토큰을 가지고 브라우저의 저장소에 토큰을 유지시키는 방식(클라이언트에 저장)이다.

# 토큰 기반 인증을 사용하는 이유

세션 기반 인증은 서버의 확장성이 떨어지고, 세션을 저장 및 유지하기 위한 서버의 자원이 필요하다. 또한 세션이 서버에 저장이 되고, 트래픽 분산을 위해서 여러 대의 서버를 사용할 때 만약 사용자가 초기 로그인할 경우 서버에서 세션을 생성하게 되는데 다음 로그인 시에도 초기 로그인 시 만들어진 세션을 참조해야 하기 때문에 처음 로그인한 서버에 요청을 보내야 한다는 단점이 있다. 반면 토큰 기반 인증은 서버에 저장하지 않아서 서버에 확장성이 상대적으로 높고 로그인을 했을 때 해당 서버에만 요청을 보내는 것이 아닌 요청이 들어왔을 때 해당 토큰이 유효한지만 체크하면 되기 때문에 어떤 서버로 요청을 보내도 상관이 없다.

# JWT 토큰을 사용하는 이유는?

(1) 데이터 크기 : JWT는 XML 기반의 SAML(Security Assertion Markup Language) 방식보다 크기가 작다.

(2) 보안성 : SMT(Simple Web Token) 방식은 대칭키 방식으로 해싱하지만 JWT와 SAML 토큰은 공개키/개인키 방식을 사용한다. 인증 과정에서 대칭키 방식은 인증 확인자가 같은 키로 데이터를 만들어 다른 인증 확인자에게 잘못 사용될 수 있다는 문제가 있다. 또한 인증 과정은 인증 확인자가 데이터를 생성할 필요 없이 확인만 하면 되기 때문에 공개키/개인키 방식이 더 적합하다.

(3) 호환성 : JSON은 대부분 언어에서 객체로 바로 변환될 수 있기 때문에 대부분의 언어에서 지원하고 있다.

# JWT 토큰 구조

(1) Header (헤더) : Token의 기본요소, 헤더에는 일반적으로 토큰의 타입과 해싱 알고리즘 명시

(2) Payload (페이로드) : 전달하려는 데이터, key-value 페어로 클레임 정보를 포함한다. 클레임의 종류로는 등록된 클레임, 공개 클레임, 비공개 클레임이 있다.

(3) Signature (시그니쳐) : 서명된 값, header와 payload를 해싱 알고리즘에 의해 계산한 결과를 포함

# JWT 동작 방식

(1) 클라이언트에서 id, pw 정보를 서버로 보냄

(2) 서버에서 id, pw 정보를 이용하여 JWT token 을 생성함

(3) 서버에서 클라이언트로 JWT token 을 보냄

(4) 클라이언트에서 서버로 서비스 요청시, JWT token 을 같이 보냄.

(5) 서버에서 서비스 처리시, JWT token 을 검증, 일치하면 서비스를 동작시켜 클라이언트로 응답

# JSON 사용 이유

서버와 클라이언트 또는 애플리케이션 처리할 데이터를 주고받을 때 자료 형식 중 대표적인 것이 XML과 JSON이 있다. 이 중 XML은 데이터 포맷 중 하나로 HTML과 유사한 마크업 언어이다. 데이터를 저장하고, 전달할 목적으로 고안되었다. 불필요한 태그들이 들어가 파일의 사이즈가 커질 뿐만 아니라 가독성도 좋지 않아 XML대신 JSON이 사용된다. JSON은 데이터 포맷 중 하나로 key와 value가 한 쌍을 이루는 구조의 객체로 구성되어 있으며 XML의 대안으로서 고안되었고 XML 대비 더 직관적이며, 작성하기 편리하다는 특징이 있다. 또 배열을 파싱할 수 없는 XML과 달리 JSON은 배열을 사용할 수 있다. 또한 프로그래밍 언어나 플랫폼에 상관없이 사용할 수 있다.

# JWT 안전하게 사용하는 방법

(1) 사용자 로그인 시 서버에서 사용자 확인 후 액세스 토큰, 리프레쉬 토큰 발급, 이 때 실제 리프레쉬 토큰 값은 DB에 저장

토큰 유효 시간 설정 : 이 때 액세스 토큰의 시간 유효기간은 짧게, 리프레쉬 토큰의 시간 유효기간은 길게 설정

(2) 서버에서 리프레쉬 토큰의 실제 값이 아닌 index값이나 해쉬 값을 액세스 토큰과 함께 클라이언트에 전달

(3) 클라이언트에서 리프레쉬 토큰 index 혹은 해쉬 값은 쿠키로 관리, 왜냐하면 가장 필수로 막아야하는 XSS 보안에 유리, 서버에서 httpOnly쿠키로 설정해서 XSS 막기, 또 추가로 secure / SameSite(Strict or Lax 모드) 옵션을 지정

  • XSS : 클라이언트 단에서 실행되는 악의적 스크립트, 가장 필수적으로 막아야하는 공격
  • httpOnly : document.cookie와 같은 자바스크립트로 쿠키를 조회하는 것을 막는 옵션)

(4) 클라이언트에서 액세스 토큰은 비공개 변수로 관리, 권한 필요 시 Authorization 헤더에 access token을 담아 요청

(5) 매 요청 시마다 액세스 토큰 + 리프레쉬 토큰 index 혹은 해쉬 값을 같이 담아서 보내어 매번 액세스 토큰을 새로 발급받는 방식을 사용

(5-1) 요청 시 항상 리프레쉬 해쉬 값 + 액세스 토큰 보냄

(5-2-1) 클라이언트 요청 시 액세스 토큰 만료면 서버 렌더링 과정 혹은 API 통신을 통해 재발급을 요청, 서버에서 리프레쉬 해쉬 값 검증 후 액세스 토큰 새로 발급해서 응답, 이 때 요청 시 쿠키에는 자바스크립트에서 접근이 불가능한(httpOnly 옵션) refresh token이 이미 담겨진 상태로 서버와 통신하게 됨

(5-2-2) 리프레쉬 토큰 만료면 DB와 다시 한번 통신하여 로그인 만료 페이지 혹은 로그아웃 상태로 렌더링을 하여준다. (ex 로그인이 만료되었습니다.)

(6) refresh token이 저장된 쿠키는 외부 경로와 자바스크립트 상에서의 접근이 불가능하여 CSRF, XSS 공격에서 모두 안전성이 확보된다. access token이 저장된 비공개 변수는 XSS, CSRF 공격을 시도할 방법이 사라지며, 토큰이 휘발되어 사라졌던 UX 문제도 해결된다.

# 토큰 만료 시 처리

액세스 토큰과 리프레쉬 토큰을 함께 사용하는 방식으로 처리할 수 있다. 액세스 토큰은 그 자체로 인증 정보를 모두 가지고 있어서 탈취되면 매우 위험한 상황이 발생할 수 있다. 그러므로 만료 기간을 지정해주어야 하는데 만료 기간이 다 했을 경우에는 액세스 토큰을 재발급 할 수 있는 리프레쉬 토큰을 사용해야한다. 리프레쉬 토큰은 새로운 액세스 토큰을 생성하는 용도로만 사용된다. 굳이 별도의 리프레쉬 토큰을 두고 새로운 액세스 토큰을 발급받도록 한 이유는 보안 때문이다. 액세스 토큰의 유효기간을 짧게 설정하고 리프레쉬 토큰의 유효기간을 길게 설정한 뒤 둘 다 서버에 전송하여 액세스 토큰으로 인증하고 만료 시 리프레쉬 토큰으로 액세스 토큰을 새로 발급받는다. 만약 공격자에 의해 액세스 토큰을 탈취 당하더라도 유효 기간이 짧기 때문에 유효 기간이 지나면 사용할 수 없고 정상적인 클라이언트는 리프레쉬 토큰으로 액세스 토큰을 재발급 받은 뒤 사용할 수 있다. 단 리프레쉬 토큰은 서버에 저장해두어야 한다고 한다. 서버에 실제 리프레쉬 토큰 값을 저장하고 index값을 쿠키나 로컬스토리지에 저장하는 방식으로 유효기간이 긴 리프레쉬 토큰이 탈취당하는 것을 방지할 수 있다고 한다. 또 index값 또한 단순 index값이 아닌 hash값을 생성해 사용하면 보안에 더욱 유리하다고 한다.


# SOP정책과 CORS

# SOP(same-origin)정책이란?

same-origin policys는 동일 출처 정책인데 동일 출처 정책은 출처(origin)에서 로드된 문서나 스크립트가 다른 출처에 자원과 상호작용하지 못하도록 제약하는 정책이다. 통신을 주고 받는 두 URL의 프로토콜, 호스트, 포트번호 모두 동일한 경우만 동일 출처가 된다. 만약 다른 출처에서 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 한다.

# CORS란?

CORS는 교차 출처 리소스 공유(Cross-Origin Resource Sharing)라고 하며 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 정책이다. 기본적인 동작 과정은 HTTP 프로토콜을 사용하여 요청을 보낼 때 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 담아보낸다. 이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin에 허용된 출처를 담아 응답하고 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.

# CORS 에러 대처 방법

(1) 서버에서 Access-Control-Allow-Origin 세팅 : 서버에서 모든 클라이언트에 요청에 대한 cross-origin HTTP 요청을 허가하는 Access-Control-Allow-Origin: 헤더를 추가하면 된다. 을 사용하게 되면 보안에 취약할 수 있으므로 출처를 명시해줄 수 있다.

(2) proxy 설정 : proxy를 설정하여 요청 출처를 바꿀 수 있다.


# XXS와 CSRF

# XSS(Cross Site Scripting)이란?

XSS은 공격자가 의도하는 악의적인 js 코드를 피해자 웹 브라우저에서 실행시키는 것이다. 희생자 클라이언트 PC에서 실행되며 사용자의 정보를 탈취하는 것이다. XSS 공격을 막는 것은 웹 보안을 위한 최소한의 조치이다.

# CSRF(Cross site request forgery)이란?

정상적인 request를 가로채 피해자인 척 하고 백엔드 서버에 변조된 request를 보내 악의적인 동작을 수행하는 공격을 의미한다. CSRF는 위조된 요청을 서버에 보내어 서버단에서 스크립트가 실행된다.

# XSS 대응방법

(1) 입/출력값 검증 : 입출력 값에 대해 목적에 맞는지 다양한 검증

(2) 보안 라이브러리 사용 : 오픈소스 보안 라이브러리를 활용하여 XSS를 방지한다.

(3) HttpOnly 속성 사용 : 스크립트에서 쿠키에 접속하는 것을 방지하는 HttpOnly 옵션을 사용한다.

(4) CSP(Content Security Policy) : 사이트에서 직접 컨텐츠별로 정책을 정의하여 사이트에서 허용한 컨텐츠에만 접근하도록 하는 브라우저 표준 보안 정책을 적용한다.

(5) 올바른 Content-Type 사용 : 적절한 Content-Type을 지정하여 악성 스크립트가 실행되지 않도록 해준다.

# CSRF 대응방법

(1) Referrer 검증 : request의 header에 존재하는 referrer 속성을 확인하여 요청을 한 페이지의 정보를 검증하고 차단하는 방법이다.

(2) Security Token(CSRF Token) 검증 : 특정 조건(로그인 등)일 때 사용자의 세션에 임의의 난수 값을 저장하고, 사용자의 요청 마다 해당 난수를 포함시켜 전송한다. 그리고 요청이 들어올 때 마다 세션에 저장된 값과 요청으로 전송된 난수값이 일치하는지 서버에서 검증하는 방법이다.

(3) Double Sumbit Cookie 검증 : 세션을 사용 못하는 환경에서 사용하는 방법으로 웹브라우저의 Same Origin 정책으로 인해 자바스크립트에서 타 도메인의 쿠키 값을 확인/수정하지 못한다는 것을 이용한 방법이다. 스크립트 단에서 요청 시 난수 값을 생성하여 쿠키에 저장하고, 동일한 난수 값을 요청 파라미터로 서버에 전송한다. 서버에서는 쿠키의 토큰 값과 요청시 들어온 파라미터의 토근 값이 일치하는 지 검사하는 방법이다.

profile
프론트엔드 개발자

1개의 댓글

comment-user-thumbnail
2023년 2월 20일

좋은 내용 감사합니다! ! !

답글 달기