JWT를 JWT스럽지 못하게 설계를?
왜?
뭘 위해서 그렇게 쓰는거야?
JWT를 Stateful하게 설계하는 이유를 설명하기위한 첫번째 포스트를 쓰기 전에
JWT에 대한 소개글을 먼저 작성하겠습니다.
세션과 달리 토큰을 Client에 저장해서 서버의 부담을 줄일 수 있다. 토큰 자체에 사용자의 권한 정보나 서비스를 사용하기 포함된다는 것.
Stateless인 환경에서 사용자 데이터를 주고 받을 수 있다.
단순히 JWT에 토큰을 클라이언트에 저장하고 요청할 때 HTTP 헤더에 토큰을 첨부하는 것만으로 데이터를 요청하고 응답을 받을 수 있다는 장점!
Header, Payload, Signature로 구성되고 각 구분은 .
으로 합니다.
JWT에 사용할 타입(JWT
라는 문자열이 들어감) 과 해시 알고리즘의 종류가 담긴다.
서버에서 첨부한 사용자 권한 정보와 데이터가 담겨있다.
정보를 담는 단위를 클레임(Claim)이라고 부릅니다.
ㅁ토큰에 대한 정보를 담기 위한 클레임들이며, 이미 이름이 등록되어있는 클레임
iss
: 토큰 발급자(issuer)
sub
: 토큰 제목(subject)
aud
: 토큰 대상자(audience)
exp
: 토큰의 만료시간(expiraton). 시간은 NumericDate 형식으로 되어있어야 하며,(예: 1480849147370) 항상 현재 시간보다 이후로 설정되어있어야한다.
nbf
: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념. NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않는다.
iat
: 토큰이 발급된 시간 (issued at)
jti
: JWT의 고유 식별자로서, 주로 일회용 토큰에 사용한다
Header, Payload를 Base64 URL-safe Encode하고 Header에 적힌 해시함수를 적용하고, 개인키(Private Key)로 서명한 전자서명이 담겨있다.
타원곡선 암호화를 한다고 가정하면 Signature값은
Sig = ECDSA(SHA256(B64(Header).B64(Payload)),PrivateKey)
발급 후의 토크의 정보는 수정할 수가 없습니다.
- 클라이언트가 사용자의 아이디, 패스워드를 통해 웹 서비스 인증 요청
- 서버에 Signed 된 JWT를 생성해서 클라이언트에 응답으로 돌려주기
- 클라이언트 가 서버에 데이터를 요청할 때마다 JWT를 Http Header에 첨부한다.
- 서버에서 JWT를 검증한다.
이때 당시 JSON 데이터를 Base 64 URL-safe Encode를 통해 인코딩을 한다.
Base 64 URL-safe Encode는 일반적인 URL에서 오류없이 사용할 수 있도록 + , / 를 - _로 나타낸다.
- 사용자가 아이디와 비밀번호로 로그인을 한다.
- 서버 측에서 사용자(클라이언트)에게 유일한 토큰을 발급한다.
- 클라이언트는 서버 측에서 전달받은 토큰을 쿠키나 스토리지에 저장해 두고, 서버에 요청을 할 때마다 해당 토큰을 서HTTP 요청 헤더에 포함시켜 전달한다.
- 서버는 전달받은 토큰을 검증하고 요청에 응답한다.토큰에는 요청한 사람의 정보가 담겨있기에 서버는 DB를 조회하지 않고 누가 요청하는지 알 수 있다.
- 쿠키/세션과 다르게 토큰 자체의 데이터 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해질수 있다.
- Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다.
- 토큰을 탈취당하면 대처하기 어렵다. (따라서 사용 기간 제한을 설정하는 식으로 극복한다)
이런 Token 의 방식중 하나인 인증에 필요한 정보를 암호화 시킨 JSON 토큰을 JWT라 부른다.
실제로 토큰을 생성하는 과정을 통해 더 이해해봅시다.
public String crateJwtToken() {
Date now = new Date();
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) // (1)
.setIssuer("fresh") // (2)
.setIssuedAt(now) // (3)
.setExpiration(new Date(now.getTime() + Duration.ofMinutes(30).toMillis())) // (4)
.claim("id", "아이디") // (5)
.signWith(SignatureAlgorithm.HS256, "secret") // (6)
.compact();
}
지금까지 JWT의 동작 과정 및 개념을 이해해보았습니다.
다음 포스팅에서 Stateless 한 JWT 를 강제로 Stateful하게 설계를 해서 XSS Attack을 막는 방법을 소개해보겠습니다.