JWT 토큰 기반 인증
토큰 기반 인증
사용자가 서버에 인증을 하는 방법은 다양하다. 스프링 시큐리티에서는 기본적으로 세션을 사용해서 인증을 한다.
하지만 내가 이번에 사용해 볼 방법은 토큰을 사용해 인증을 하는 방식이다.
토큰은 서버에서 클라이언트를 구분하기 위한 유일한 값이다.
서버가 토큰을 생성해서 제공하면, 클라이언트가 이 토큰을 가지고 여러 요청을 토큰과 함께 한다. 서버는 토큰을 보고 유효한 클라이언트인지 검증을 한다.
토큰을 전달하고 인증 받는 과정
- 클라이언트가 회원 정보를 서버에게 전달하면서, 인증을 요청
- 서버는 회원 정보 유효성 검사를 한다. 유효하면 토큰을 발급한다.
- 클라이언트는 서버에서 발급 받은 토큰을 저장한다.
- 이후 인증이 필요한 API를 사용할 때 요청과 함께 토큰을 보낸다.
- 서버에서 토큰을 검증한다.
- 토큰이 유효하면 요청을 처리한다.
토큰 기반 인증의 특징 3가지
무상태성, 확장성, 무결성
- 무상태성 : [서버에서 데이터를 유지하기 위해서는 자원이 소모된다]
- 사용자의 정보가 담긴 토큰을 서버가 아닌 클라이언트가 관리 ← 이것을 상태 관리라고 한다.
- 서버에서 데이터(상태) 관리할 필요 X, 불필요한 자원 소모가 없다.
- 확장성 [무상태성은 확장성에 영향을 준다]
- 서버 확장 시 상태 관리에 신경 쓸 필요가 없다.
- 만약 세션 인증을 사용했다면 각각의 API에서 인증을 해야 하기 때문에 부담이 된다. 토큰 인증은 클라이언트가 토큰을 관리하기 때문에 하나의 토큰으로 여러 API호출이 가능하다.
- 무결성
- HMAC(hase-based message authentication) 기법이라고도 부른다.
- 토큰을 발급한 후에는 변경을 할 수 없다. 한 글 자라도 달라지면 hashCode가 달라지기 때문에 서버에서 요청을 처리하지 않는다.
JWT(Json Web Token)
표준을 따르고 있으며, JSON 객체를 사용하여 정보를 전달합니다.
HTTP 요청 헤데 중 Authorization 키 값에 Bearer + JWT 토큰 값을 넣어서 사용한다.
JSON 웹 토큰을 사용해야 하는 이유
JSON은 XML보다 간단한 구조를 가진다. 따라서 인코딩 시 크기가 작다.
이로 인해 JWT는 HTML 및 HTTP 환경에서 전달되는 좋은 선택이 된다.
사용자 인증에 필요한 모든 정보는 토큰 자체에 포함하기 때문에 별도의 인증 저장소가 필요 없다
JWT토큰 구조
JWT는.(dot)을 기준으로 헤더(header), 내용(payload), 사인(signature)으로 이루어져 있다.
헤더.내용.사인
xxxxx.yyyyy.zzzzz
헤더(header)
토큰의 타입, 해싱 알고리즘 정보를 담는다. 그런 다음 이 JSON은 Base64 Url로 인코딩 되어 JWT의 첫 번째 부분을 구성합니다.
{
"typ": "JWT", //거의 고정
"alg": "HS256"
}
typ : 토큰의 타입을 지정합니다.
alg : 해싱 알고리즘을 지정합니다.
내용(payload)
토큰과 관련된 정보를 담는다. 단순 Base64로 인코딩 된 파트이기 때문에 누구나 디코딩하여 데이터를 볼 수 있다. Password 같은 민감한 정보는 담으면 안 된다.
클레임
- 정보의 한 ‘조각’을 클레임(claim)이라고 한다.
- key:value 한 쌍으로 이루어져 있다.
- 엔티티(일반적으로 사용자) 및 추가 데이터에 대한 설명이다.
Playload 전체적인 모습
{
"iss":"test@gmail.com", //등록된 클레임
"iat":"2323232323", //등록된 클레임
"exp":"3434343434", //등록된 클레임
"https":"//test.com/jwt_claims/is_admin" :true, //공개 클레임
"email":"test@gmail.com", //비공개 클레임
"hello":"안녕하세요!" //비공개 클레임
}
종류(등, 공, 비)
- 등록된 클레임
- 공개 클레임
- 비공개 클레임
등록된 클레임(registered claim)
- 토큰에 대한 정보를 담는 데 사용한다. 필수는 아니지만 권장되는 미리 정의된 클레임 집합
- JWT가 압축되도록 되어 있으므로 클레임 이름은 3자로 정의된다.
- ex) iss(토큰 발급자), sub(토큰 제목), aud(토큰 대상자) 등 여러 가지가 있다.
{
"iss":"test@gmail.com", //등록된 클레임
"iat":"2323232323", //등록된 클레임
"exp":"3434343434", //등록된 클레임
}
[다른 등록된 클레임에 대해 궁금하신 분을 위한 자료]
https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
RFC 7519: JSON Web Token (JWT)
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JS
datatracker.ietf.org
공개 클레임(public claim)
- JWT를 사용하는 사람들이 원하는 대로 정의할 수 있다.
- 하지만, 충돌을 방지하려면 충돌 방지 ‘네임스페이스를 포함하는 URI’로 정의해야 한다.
"https":"//test.com/jwt_claims/is_admin" :true, //공개 클레임
비공개 클레임(private claim)
- 사용에 동의한 당사자(보통 클라이언트 ↔ 서버) 간에 정보를 공유하기 위해 생성된 맞춤 클레임이다.
- 등록되거나 공개된 클레임이 아니다. 이름이 중복되어 충돌이 될 수 있으니 사용할 때에 유의해야 한다.
{
"sub": "1234567890",
"name": "test lee",
"admin": true
}
서명(signature)
서명은 메시지가 도중에 변경되지 않았는지 확인하는 데 사용되며, 개인 키로 서명된 토큰의 경우 JWT의 보낸 사람이 누구인지 확인할 수도 있다.
서명 생성
- 인코딩 된 헤더, 인코딩된 페이로드, 비밀, 헤더에 지정된 알고리즘을 가져와서 서명해야 합니다.
SHA256 알고리즘을 사용하려는 경우 서명은 다음과 같은 방식으로 생성된다.
- 제일 마지막의 secret의 경우 사용자가 지정한 비밀 코드(너무 간단하지 않게 작성해야 한다.)
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
액세스 토큰과 리프레시 토큰
딱 봐도 어떤 역할을 할지 감이 온다. 매우 직관적인 네이밍이다.
- 엑세스 토큰 : 처음 API 호출 시 사용하는 토큰
- 리프레시 토큰 : 엑세스 토큰이 만료되었을 때 엑세스 토큰을 재발급 목적으로 사용하는 토큰
엑세스 토큰과 리프레시 토큰을 나눈 이유
만약, 엑세스 토큰의 유효기간이 하루라고 할 때, 중간에 토큰을 탈취당하는 일이 발생한다면
토큰의 특성상 회수를 할 수 없기 때문에 사용자의 민감한 정보에 마구 접근해도 막을 수가 없다.
이를 예방하기 위해 액세스 토큰의 유효기간은 짧게 (약 1시간)
리프레시 토큰의 유효기간은 비교적 길게(약 2주) 설정한다.
이런 방식을 통해서 엑세스 토큰이 탈취를 당했더라고 만료 주기가 짧기 때문에 피해를 줄일 수 있다.
최종적인 JWT의 모습
서명된 토큰의 경우 변조로부터 보호되지만 누구나 읽을 수 있다. 암호화되지 않은 경우 JWT의 페이로드 또는 헤더 요소에 비밀 정보를 넣지 않고, 간단한 정보만 제공한다.
[참고 자료]
https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
스프링 부트 3 백엔드 개발자 되기: 자바 편