[JWT]JWT 개요
이번 포스트에서는 JWT의 개념과 기본 동작에 대해 살펴보자.
Session Cookie와 JWT를 통한 인증의 차이
Session Cookie 활용
session cookie는 전통적인 server의 인증 방식이다.
- 클라이언트가 id/pass를 가지고 서버로 로그인 요청을 한다.
- 서버는 db server를 이용해 해당 사용자가 valid한지 점검하고 결과를 반환한다.
- 로그인 성공 시 서버는 session store에 세션 공간을 할당하고 이 공간에 접근할 수 있도록 session cookie를 response로 내려보낸다.
- 작업을 요청할 때 client는 session cookie를 request에 포함시켜 서버로 전송한다.
- 서버는 이 cookie 값으로 로그인한 적이 있는지 session store에 확인한다.
- 로그인한 사용자라면 요청을 처리한다.
session cookie 방식의 핵심은 로그인 정보는 session store에 존재하고 이 정보에 접근할 수 있는 키만 cookie로 전달된다는 점이다. 따라서 실제 사용자의 정보를 확인하기 위해서는 계속 session store에 질의 해야 한다.
MSA 형태의 프로젝트 증가에 따른 기존 세션 관리의 문제점
최근에는 서버 성능 개선을 위한 scale out 작업이나 MSA 형태의 프로젝트들이 많이 증가하면서 session cookie 기반의 인증은 조금씩 문제점을 보이게 된다.
https://goodteacher.tistory.com/712
Token 활용
결국 생각해낸 새로운 방식은 Token 기반의 인증이다.
- 클라이언트가 id/pass를 가지고 서버로 로그인 요청을 한다.
- 서버는 db server를 이용해 해당 사용자가 valid한지 점검하고 결과를 반환한다.
- 로그인 성공 시 서버는 인증 정보가 담긴 token을 생성 후 session cookie를 response로 내려보낸다.
- 작업을 요청할 때 client는 token을 request에 포함시켜 서버로 전송한다.
- 서버는 이 token이 valid 한지 확인한다. 토큰 자체에 로그인 정보를 포함되어있기 때문에 제대로된 토큰만 전달된다면 굳이 session store에 확인할 필요가 없다.
- 로그인한 사용자라면 요청을 처리한다.
두 가지 방법의 비교
두 방법을 비교해서 정리해보자.
항목 | session cookie | Token |
인증 정보의 저장 위치 | 서버 | 클라이언트 |
인증을 위해 클라이언트가 서버로 보내는 것 | session cookie | 서명된 token 자체 |
사용자의 요청에 대한 서버의 인증 확인 | session store에 cookie에 대한 사용자의 정보가 있는지 확인(redis db query 수행 등) | token이 잘 서명되었는지만 판단 |
서버가 사용자의 인증 정보 수정, 로그아웃 처리 가능 여부 | 처리 가능(세션은 서버에서 관리) | 불가(토큰은 클라이언트가 관리) |
공격 가능성 | 쿠키 탈취에 의한 CSRF 공격 | 토큰 탈취, 비밀 키 유출 |
어떤 방법이 더 좋다기 보다는 장단점을 기억해주는 것이 좋다. 과거의 Monolithic 서버에서는 session cookie를 많이 이용했었고 scale out 상태에서의 로드벨런싱을 처리하기 위해 REDIS 서버를 사용했었다. 최근의 REST 방식의 서비스들은 MSA(Micro Service Architecture) 기반으로 많이 작성하기도 하고 여러 서버를 활용하는 일이 많아지기도 했고 REDIS의 활용도 네트워크 부담이 되기 때문에 그냥 서버에서 토큰만 점검하면 되는 JWT를 많이 이용하는 추세다.
마지막으로 정리하면 토큰은 무상태이며 확장성이 좋다. 애초에 HTTP의 특성이 무상태인데 여기에 적합하며 무상태이기 때문에 서버의 확장에 전혀 부담이 없다.
JWT
JWT란?
JWT(Json Web Token)은 RTC7519에 선언된 개방형 업계 표준으로 두 당사자 간에 클레임(데이터)를 안전하게 표현하기 위해 사용된다. JSON 기반이기 때문에 대부분의 언어, 플랫폼에 독립적으로 사용할 수 있는 장점도 있다.
JWT는 Header, Payload, Signature 3개의 부분으로 구성된 토큰으로 각 부분은 .로 연결된다.
- Header: 일반적으로 토큰의 유형(typ)와 사용중인 서명 알고리즘(alg)으로 구성된다. Header는 Base64로 인코딩되어 토큰의 첫 번째 부분을 구성한다.
- Payload: 토큰의 두번째 부분은 Payload인데 여기는 통상 클레임(claim)이 담긴다. 클레임은 일반적으로 사용자 정보에 해당하는 엔티티와 추가 데이터에 대한 내용으로 registed, public, private 3종류가 있다. Payload도 Base64로 인코딩된어 전달된다.
- registed: name(토큰 이름), exp(만료시간), sub(대상), iss(발행자) 등 필수 사항은 아니지만 미리 정의된 권장사항이다. JWT는 간결해야 하므로 클레임 이름은 3글자만 사용한다.
- public: JWT를 사용하는 사람들이 마음대로 정의할 수 있는 내용이다. 외부에 공개되어도 괜찮은 정보들이며 이름 충돌을 방지하기 위해 URI 형식을 취한다.
- private: 사용에 동의한 당사자 간에 정보를 공유하기 위해 만든 클래임으로 서비스 내부적으로 사용하는 커스텀 데이터를 담는다.
- 서명된 토큰의 경우 정보의 위/변조는 막을 수는 있지만 누구나 읽을 수 있다. 따라서 페이로드 또는 헤더에 비밀 정보를 넣으면 안된다.
{
// Registered Claims
"iss": "auth.mycompany.com",
"exp": 1516239022,
// Public Claims
"https://api.mycompany.com/permissions": ["read", "write"],
// Private Claims
"internal_user_id": "U123456",
"team_code": "DEV-KR"
}
- Signature: 인코딩된 헤더, 인코딩된 페이로드와 함께 서버에서 관리하는 secret을 헤더에 지정된 알고리즘으로 서명해서 사용한다. 예를 들어 HMAC SHA256은 다음의 방식으로 서명을 생성한다.
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
JWT 동작 절차
JWT는 다음의 절차를 통해서 동작한다.
- 사용자는 id/password를 서버에 제공해서 인증을 요청한다.
- 서버는 인증이 성공했다면 Header, Payload, Signature로 구성된 토큰을 생성한다. signature를 구성할 때는 Header와 Payload를 각각 base64로 인코딩 후 "비밀키"를 이용해서 HS256으로 해싱한다.
- 생성된 토큰을 클라이언트에 전달한다.
- 클라이언트(브라우저)는 토큰을 스토리지 등 저장소에 저장한다.
- 서버의 인증이 필요한 API 호출 시 JWT 토큰을 전달한다.
- 서버는 전달된 토큰에서 header와 payload를 추출 후 비밀 키와 다시 hasing 해서 전달된 signature와 비교해본다. 이를 통해 서명의 위조를 판단한 수 있다.
- 토큰이 valid 하다고 판단되면 요청한 API의 결과를 반환한다.