[JWT] Json Web Token으로 인증 이해하기

이 글은 [Must Have] 코드팩토리의 플러터 프로그래밍 2판에서 발췌했습니다.
골든래빗 출판사
코드팩토리(최지호) 지음

서버와 통신하는 사용자의 정보와 유효성을 검증할 수 있는 기능인 인증(Authentication)에 대해 알아봅니다. 다양한 인증 방법이 있지만 그중에서 가장 많이 사용되는 방법은 JWT(Json Web Token) 토큰을 사용한 인증입니다.

 

[Authentication] JWT로 인증 이해하기

 

인증(Authentication)은 서버와 통신하는 사용자의 정보와 유효성을 검증하는 기능입니다. 이전 장에서 제작해 본 캘린더 앱을 통해 인증이 왜 필요한지 알아보겠습니다. 《코드팩토리의 플러터 프로그래밍》에서 만든 캘린더 앱은 드리프트 플러그인으로 로컬 데이터베이스를 사용하고 있습니다. 즉, 사용자 핸드폰에서 데이터베이스를 직접 관리하기 때문에 사용 중인 하나의 핸드폰에 저장한 정보는 한 명의 사용자가 저장한 정보라고 가정해도 무방합니다.

 

▼ 사용자 핸드폰

 

❶ 사용자가 드리프트 API를 이용해 캘린더 앱에서 일정 관련 정보를 생성, 조회, 업데이트, 삭제합니다. ❷ 드리프트 데이터베이스에서 새로 입력받은 정보를 반영하거나 요청받은 정보를 반환합니다. 이때 하나의 캘린더 앱당 드리프트 데이터베이스는 1:1로 매핑되어 있습니다. 즉, 하나의 앱당 데이터베이스는 하나입니다.

하지만 앱에 서버를 연결하게 되면 상황은 매우 복잡해집니다. 《코드팩토리의 플러터 프로그래밍》 19장에서 작업한 캘린더 앱의 서버 연동 작업은 HTTP 프로토콜을 통해 요청하는 법을 배우는 게 목표였기 때문에 사용자 정보 없이 직접 서버와 연동하는 작업을 했습니다. 다시 말해 단 하나의 기기만 서버에 연동한다는 가정하에 작업하였습니다. 그렇기 때문에 여러 기기에서 캘린더 앱을 실행하면, 캘린더 앱을 실행한 모든 기기에서 일정 데이터를 공유하게 됩니다. 개인 일정 관리를 목표로 기능을 만들었기 때문에 일정 데이터를 공유하는 것은 목적과 맞지 않습니다. 그러므로 A, B 사용자가 각각 생성한 개인 일정이 앱을 실행하면서 A, B 모두에게 공유된다면 개인 일정 앱이라고 볼 수 없습니다.

 

▼ 일정 데이터 생성할 때

 

❶ 사용자 A와 사용자 B가 각각 다른 일정 A와 일정 B를 생성합니다. ❷ 생성된 일정 A와 일정 B는 서버의 데이터베이스에 저장됩니다.

 

▼ 일정 데이터 가져올 때

 

❶ 사용자 A와 사용자 B가 각각 일정 정보를 서버에 요청합니다. ❷ 요청을 받았지만 사용자를 식별할 수 있는 방법이 없어서 사용자 A와 B는 일정 A와 일정 B 정보를 모두 응답받습니다. 즉, 본인이 생성한 일정뿐만 아니라 타인이 생성한 일정도 응답받게 됩니다.

개인 일정 관리 앱의 일반적인 설계는 누가 생성한 일정인지 각 기기에 로그인한 사용자별로 구분하고 본인이 생성한 일정 정보만 반환해주는 게 올바른 형태입니다. 이때 필요한 기능이 바로 인증입니다. 특정 사용자로 로그인하고 로그인한 사용자의 정보를 기반으로 일정을 생성하면 일정 정보를 요청할 때 요청하는 사용자가 생성한 일정만 구별해서 반환해줍니다.

 

▼ 인증 절차 진행

 

❶ 사용자 A와 사용자 B가 각각의 ID와 비밀번호를 입력해서 로그인합니다. ❷ 로그인 정보를 기반으로 서버에서 사용자 인증을 진행하고 인증 정보를 반환합니다. 이 인증 과정에서 JWT를 활용합니다.

 

▼ 일정 데이터 생성할 때

 

❶ 사용자 A와 사용자 B가 각각 다른 일정 A와 일정 B를 생성합니다. 이때 사용자 A와 사용자 B는 인증 과정에서 발급받은 JWT를 일정 데이터와 함께 전송합니다. ❷ 일정 A와 일정 B를 저장할 때 각각의 일정 정보와 함께 전송된 JWT를 기반으로 어떤 사용자가 생성한 일정인지에 대한 정보가 함께 데이터베이스에 저장됩니다.

 

▼ 일정 데이터 가져올 때

 

❶ 사용자 A와 사용자 B가 각각 일정 정보를 서버에 요청합니다. 이때 인증 정보가 있는 JWT 토큰과 함께 요청을 보냅니다. ❷ 서버에서는 JWT를 분석하여 어떤 사용자가 요청을 보냈는지 알수 있습니다. 서버에 생성된 일정은 어떤 사용자가 생성한 일정인지 이미 구분되어 저장되어 있습니다. 일정 데이터 생성에서 JWT를 전송하여 인증 정보를 바탕으로 정보가 저장되었기 때문입니다. 따라서 각 사용자별로 본인이 생성한 일정만 응답받을 수 있습니다.

 

1. JWT란?

어떤 사용자가 요청을 하는지 알기 위해서 요청을 보낼 때마다 사용자에게 ID와 비밀번호를 입력하게 한다면 최악의 UX를 갖게 하는 앱을 제작하게 될 것입니다. 그렇기 때문에 사용자가 한 번 로그인을 하면 요청을 보낼 때마다 ID와 비밀번호를 매번 입력할 필요 없이 기입력된 사용자 정보를 서버로 전달할 방법이 필요합니다. 현대에는 이런 번거로움을 줄이기 위해 보편적으로 JWT를 사용해서 사용자 정보를 서버와 공유합니다.

JWT는 헤더(header), 페이로드(payload), 시그니처(signature) 세 가지 요소로 이루어져 있습니다. 이 세 가지 요소를 이용해서 토큰의 정보, 사용자 정보 그리고 토큰의 유효성에 대한 정보를 담게 됩니다. 이 세 요소는 각각 URL에 전송 가능한 형태인 URL base64로 인코딩되어 있으며 ‘.’을 이용하여 하나의 String값으로 묶어서 사용합니다.

 

▼ Base64 인코딩 된 JWT 토큰

 

▼ Base64 디코딩 된 JWT 토큰

 

❶ 헤더에는 토큰에 대한 정보가 담깁니다. 예를 들면 토큰의 시그니처가 어떤 알고리즘으로 암호화됐는지, 토큰의 타입이 무엇인지 저장합니다.

❷ 페이로드에는 데이터베이스상의 사용자 ID, 사용자 이름, 토큰의 만료 기간 등 사용자 정보가 담깁니다.

❸ 시그니처에는 JWT의 유효성을 검증할 수 있는 정보가 담겨 있습니다. 토큰을 생성할 당시 헤더를 base64로 인코딩한 값과 페이로드를 base64로 인코딩한 값 그리고 사용자가 지정한 ❹ Secret값을 모두 합쳐서 ❶ 에서 정한 알고리즘으로 암호화합니다. 만약 인증하는 과정에서 해커가 JWT를 탈취한 후 페이로드나 헤더값을 한 글자라도 변경하면 똑같은 알고리즘으로 암호화한다 해도 다른 시그니처값이 반환됩니다. 이 변경된 시그니처값과 기존의 시그니처값 대조를 통해 토큰의 변형 여부를 알 수 있습니다. 그렇기 때문에 ❹ Secret값은 탈취되지 않고 JWT가 탈취되어 토큰 내부 정보가 변경된다면 토큰의 변형 여부를 시그니처값 대조를 통해 알 수 있으므로 서버 요청에 사용할 수 없습니다.

 

2. 액세스 토큰과 리프레시 토큰

JWT를 이용한 인증은 액세스 토큰(Access Token)과 리프레시 토큰(Refresh Token) 두 가지를 사용하는 방법이 일반적입니다. 액세스 토큰은 보호된 정보에 접근할 수 있는 권한 부여에 사용됩니다. 서버에 인증 정보를 보내서 리소스를 가져오거나, 변경, 생성, 삭제하는데 이용할 수 있습니다. 리프레시 토큰은 처음 로그인을 하면 액세스 토큰과 동시에 발급되는 토큰입니다. 리프레시 토큰은 액세스 토큰의 유효기간이 만료됐을 때 재발급받을 수 있도록 사용하는 특수한 토큰입니다. 이렇게 두 개의 토큰을 사용하는 이유는 무엇일까요?

액세스 토큰은 사용 빈도가 높아 탈취당할 가능성이 매우 큰 토큰입니다. 만약 해커가 액세스 토큰을 탈취하면 해당 액세스 토큰의 사용자인 척하며 서버에 정보를 요청할 수 있습니다. 그래서 액세스 토큰은 탈취되어도 해커가 오래 사용하지 못하도록 액세스 토큰 자체의 유효기간을 짧게 지정하여 문제를 해결합니다.

리프레시 토큰은 액세스 토큰을 폐기하고 새로운 액세스 토큰을 발급받을 때 사용합니다. 물론 해커가 리프레시 토큰을 탈취하면 액세스 토큰을 무한하게 발급받을 수 있습니다. 하지만 리프레시 토큰은 액세스 토큰과 비교했을 때 자주 사용하지 않아서 탈취당할 가능성이 낮습니다.

이렇게 탈취당할 확률이 높은 액세스 토큰은 유효기간을 짧게 지정하고, 탈취당할 확률이 낮은 리프레시 토큰은 유효기간을 길게 줘서 보안을 챙깁니다.

 

3. JWT를 이용한 인증 절차

JWT 인증 절차는 다음 세 가지 상황만 이해하면 됩니다. 첫 번째로 토큰을 발급받는 과정입니다. 사용자가 ID와 비밀번호를 입력하고 리프레시 토큰과 액세스 토큰을 발급받습니다. 두 번째는 액세스 토큰을 사용할 때입니다. 액세스 토큰을 이용하여 사용자 정보와 함께 서버에 요청을 보냅니다. 마지막으로 유효기간이 만료된 액세스 토큰을 서버에 요청보낸 상황입니다. 만료된 액세스 토큰을 받은 서버는 요청을 받아들이지 않으며, 401 에러가 발생합니다. 이를 기반으로 사용자는 리프레시 토큰을 이용하여 액세스 토큰을 재발급받고 새로운 토큰으로 기존 요청을 다시 보냅니다. 과정을 이해하기 쉽도록 개발 프로세스를 설명할 때 사용하는 플로우 차트를 활용하여 인증 절차를 알아보겠습니다.

 

리프레시 토큰과 액세스 토큰을 발급받을 때

 

❶ 아이디와 비밀번호를 서버로 전송합니다. 이때 아이디와 비밀번호는 ‘ID:PASSWORD’ 형태로 base64로 인코딩한 다음 헤더에 authorization: ‘Basic {인코딩된 결과}’ 형태로 전송합니다. ❷ 헤더에 넣어준 정보를 서버에서 base64 디코딩한 후 아이디와 비밀번호를 검증합니다. ❸ 아이디와 비밀번호 검증에 성공하면 리프레시 토큰과 액세스 토큰을 응답으로 보내줍니다.

 

액세스 토큰을 사용할 때

 

❶ 헤더에 액세스 토큰을 authorization: ‘Bearer {토큰}’ 형태로 포함해서 API 요청을 합니다. ❷ 액세스 토큰의 유효성을 검증합니다. ❸ 액세스 토큰을 기반으로 데이터를 응답해줍니다.

 

유효기간이 만료된 액세스 토큰으로 서버에 요청을 보냈을 때

 

❶ API 요청을 보낼 때 유효기간이 만료된 액세스 토큰을 함께 보냅니다. ❷ 서버에서 액세스 토큰을 검증하지만 만료된 토큰입니다. ❸ 서버는 상태 코드 401 에러를 응답하며 만료된 토큰임을 알립니다. ❹ 사용자는 리프레시 토큰을 포함하여 헤더에 authorization: ‘Bearer {토큰}’ 입력해서 액세스 토큰 재발급을 URL에 요청보냅니다. ❺ 서버에서 리프레시 토큰을 검증하고 새로운 액세스 토큰을 발급합니다. ❻ 신규 액세스 토큰을 응답으로 보내줍니다. ❼ 새로운 액세스 토큰을 이용하여 ❶ 에서 실패한 요청을 다시 보냅니다. ❽ 액세스 토큰을 서버에서 검증합니다. ❾ 검증된 액세스 토큰을 기반으로 응답을 보내줍니다.

 

지금까지 사용자를 식별하는 인증 시스템을 구축할 때 가장 많이 사용되는 JWT가 무엇인지, JWT를 통한 인증이 어떤 과정으로 진행되는지 알아보았습니다.

최지호(코드팩토리) 
임페리얼 칼리지 런던을 졸업하고 계리 컨설팅 회사 밀리만(Milliman) 한국 지사에서 소프트웨어 엔지니어로 일했습니다. 현재 주식회사 코드팩토리를 창업하여 개발을 하면서 초보자뿐만 아니라 현직 개발자에게도 유용한 개발 강의를 제작합니다. 밀리의서재 플러터 전환 차세대 프로젝트를 리드했습니다.

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내