JWT 토큰이란
1. JWT 토큰 소개
JWT(JSON Web Token)는 인증에 필요한 정보를 안전하기 전달하기 위한 토큰입니다. 쉽게 얘기하여 로그인한 사용자인지 확인하기 위한 토큰입니다. 놀이동산에서 손목에 차는 팔찌와 비슷한 역할을 합니다. 이러한 토큰이 필요한 이유는 HTTP가 무상태(stateless) 프로토콜이기 때문입니다.
개구리 캐릭터 이름은 개리, 곰 캐릭터는 소울곰입니다. 개리가 후드티가 얼마인지 물어봅니다. 그리고 소울곰이 잘 대답을 해주었죠. 개리가 이어서 2개 달라는 요청을 보냅니다. 여기서 문제가 생깁니다. 소울곰은 요청을 보낸 사람이 누구인지 모릅니다. 주는 요청에 따라 응답을 할 뿐이죠. 이러한 문제를 해결하기 위해 토큰을 사용합니다. 이 토큰으로 사용자를 식별할 수 있습니다.
참고로 이렇게 토큰을 이용하지 않고 매 요청마다 ID와 PW를 제공하여 사용자를 식별하게 할 수도 있습니다. 실무에서 사용하진 않지만, 처음에 공부할 때에는 이 방식도 썬더클라이언트로 한 번 경험해보시길 권하드립니다. 우리 수업에서는 JWT 토큰을 사용하여 사용자를 식별하겠습니다.
2. JWT 토큰 구조
JWT(JSON Web Token)는 세 가지 주요 구성 요소로 구성됩니다.
- 헤더 (Header): 토큰의 유형과 해싱 알고리즘을 지정합니다.
- 페이로드 (Payload): 토큰에 포함할 클레임(Claim) 정보를 포함합니다. 클레임(Claim)은 토큰에 담을 정보로, 사용자에 대한 정보나 토큰의 유효 기간 등을 포함합니다.
- 서명 (Signature): 토큰의 유효성을 검증합니다. 서명은 헤더의 인코딩 값과 페이로드의 인코딩 값을 합친 후, 비밀 키로 해싱하여 생성합니다.
이 3가지 구성 요소가 아래와 같이 .
으로 연결되어 JWT 토큰이 됩니다.
xxxxx[Header].yyyyy[Payload].zzzzz[Signature]
xxxxx[Header].yyyyy[Payload].zzzzz[Signature]
각 부분은 토큰의 구조와 정보 전달을 위해 중요한 역할을 합니다. 이러한 구성 요소를 함께 사용하여 JWT는 안전하고 효율적인 방식으로 정보를 전달하고 검증합니다. 기본 구조는 아래와 같은 형태입니다.
아래 홈페이지에서 값을 바꿔 토큰 값이 어떻게 변하는지 확인할 수 있습니다.
JWT.IO어떻게 이 값을 프론트엔드나 벡엔드 개발자가 활용하는지 파이썬으로 간단한 실습을 해보도록 하겠습니다. 위 홈페이지에 들어가서 "name"을 "fastapi"로 바꾸고 바뀐 토큰 값을 복사해주세요.
페이로드 부분은 등호 2개 추가해야 합니다.
이렇게 하면 프론트엔드와 백엔드에서 모두 페이로드 부분을 읽을 수 있습니다. 이를 통해 알 수 있는 것은 2가지입니다. 중요한 포인트이니 꼭 기억해주세요.
- 사용자 정보를 암호화하기 위한 알고리즘은 아닙니다. 프론트엔드나 백엔드에서 모두 해당 정보를 볼 수 있습니다. 해당 정보가 탈취되면 사용자로 위장하거나 그 정보를 볼 수 있습니다. 그렇기 때문에 해당 토큰은 HttpOnly 를 이용하여 탈취를 어느정도 방어할 수 있습니다.
- 토큰이 변조가 불가하다는 것입니다. 앞에 문자열이 바뀌면 뒤에 서명이 바뀝니다. 이 서명을 통해 이것이 변조되었는지 변조되지 않았는지 체크할 수 있습니다. 따라서 만약 로그인한 사용자가 1번이고, 이름이 "fastapi"라고 한다면, 이를 변조할 수는 없습니다. 변조하면 서명이 바뀌기 때문입니다.
JWT 토큰은 주민등록증과 비슷합니다. 주민등록증에는 이름, 주민번호, 주소 등이 적혀 있습니다. 이 정보를 토대로 해당 사람이 누구인지 확인할 수 있습니다. 그러나 주민등록증을 탈취하면 그 정보를 알 수 있습니다. 또한 얼굴이 비슷한 분이 탈취했다면 그 정보를 활용할 수도 있습니다. 다만 그렇다 해서 탈취한 주민등록증에 주민등록 번호를 바꾸게 되면 어떻게 될까요? 주민등록증을 누군가 조회한다면 조회해서 안나올겁니다. JWT 토큰도 마찬가지입니다. 토큰을 탈취하면 정보를 볼 수 있고, 위장을 할 수도 있습니다. 다만, 변조는 되지 않습니다.
완벽한 보안은 없습니다. 열리지 않는 자물쇠는 없다
라는 말처럼, 우리는 열기 힘든 자물쇠를 만드는 것입니다. 어떤 기술을 채택할 때 대부분 기술은 장점과 단점이 공존합니다. 따라서 우리 서비스에 어떤 보안 기술이 적합할지 비교해보아야 합니다.
3. JWT 토큰 발행 프로세스
JWT 토큰을 발행하는 프로세스는 다음과 같습니다.
- 사용자 로그인과 토큰 발행: 사용자가 로그인을 하면 서버는 사용자 정보를 확인하여 토큰을 발행합니다.
- 토큰 전송: 서버는 사용자에게 토큰에 담아 전송합니다. 이때 사용자는 적절한 곳에 토큰을 저장합니다.
- 홈페이지 활동: 사용자는 홈페이지에서 활동을 합니다. 이때 서버는 토큰을 확인하여 사용자를 식별합니다. 장바구니, 게시글 작성, 댓글 작성 등을 할 수 있습니다. 모든 요청에는 토큰이 필요합니다.
- 토큰 만료: 토큰은 만료 기간이 있습니다. 만료 기간이 지나면 서버는 사용자를 로그아웃 시키고, 토큰이 만료되었다는 애러를 반환합니다.
- 토큰 갱신: 토큰이 만료되면 리프레시 토큰을 이용하여 토큰을 갱신하려는 요청을 시도합니다.
- 토큰 갱신 성공: 서버는 리프레시 토큰을 확인하여 토큰을 갱신합니다. 이때 사용자는 새로운 토큰을 받습니다. 리프레시 토큰도 만료 기간이 있습니다. 만약 리프레시 토큰이 만료되면 로그인을 다시하라는 애러를 반환합니다. 이 경우 1번으로 되돌아가게 됩니다.
4. 토큰 사용방식
토큰을 어떻게 프론트엔드에서 주고 받는지 테스트해보겠습니다. 이 예제는 실제 JWT 토큰의 방식을 따르고 있진 않으며, 이해를 돕기 위해 간소화 했놓은 것입니다. 아래 위니브 API 명세를 참고해주세요.
위니브 API 명세프론트엔드에서는 아래와 같이 토큰을 받아옵니다. about:blank
로 접속하여 개발자 도구를 연 다음 아래 코드를 입력해주세요. 여기서 13
은 여러분이 변경 가능한 값입니다.
fetch("https://eduapi.weniv.co.kr/13/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: "test123",
password: "test1234",
}),
})
.then((response) => response.json())
.then((json) => console.log(json))
.catch((error) => console.error(error));
fetch("https://eduapi.weniv.co.kr/13/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: "test123",
password: "test1234",
}),
})
.then((response) => response.json())
.then((json) => console.log(json))
.catch((error) => console.error(error));
위 코드는 유저를 생성하는 코드입니다. 유저를 생성했으니 이제 로그인을 해보겠습니다.
fetch("https://eduapi.weniv.co.kr/13/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: "test123",
password: "test1234",
}),
})
.then((response) => response.json())
.then((json) => console.log(json))
.catch((error) => console.error(error));
fetch("https://eduapi.weniv.co.kr/13/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: "test123",
password: "test1234",
}),
})
.then((response) => response.json())
.then((json) => console.log(json))
.catch((error) => console.error(error));
로그인을 하면 토큰을 받을 수 있습니다. 이 토큰을 이용하여 앞으로 회원 전용 게시판, 장바구니 등을 이용할 수 있습니다. 그러면 이 토큰을 어딘가에 저장해야 하는데, 저장하는 방식은 크게 2가지가 있습니다.
- 로컬 스토리지: 브라우저에 저장하는 방식입니다. 브라우저를 종료하면 토큰이 사라집니다.
- 쿠키: 브라우저에 저장하는 방식입니다. 브라우저를 종료해도 토큰이 사라지지 않습니다.
여기서는 따로 저장하는 것까지 실습하진 않습니다. 이제 프론트엔드에서 토큰을 받아오는 방법을 알았으니, 이제 백엔드에서 토큰을 발행하고 검증하는 방법을 알아보겠습니다.