Spring Security 자체 Login 및 OAuth2 Login with Jwt (2) - JWT란?
공식 문서를 토대로 작성했습니다.
jwt.io
자체 Login과 OAuth2 로그인 시 세션 방식이 아닌 JWT를 사용할 것이기 때문에 JWT에 대해 알아보겠습니다.
JSON Web Token이란?
JWT는 RFC 7519 표준으로 서버-클라이언트 간 JSON 형식으로 주고받을 수 있는 방식으로,
JWT를 발급하기 전에 서명을 하기 떄문에 신뢰할 수 있다.
JWTs는 HAMC 알고리즘 혹은 비대칭키(RSA 또는` ECDSA) 방식으로 서명할 수 있다.
JWT를 암호화 할 수도 있지만, 해당 문서에서는 토큰을 서명하는 방식을 기준으로 진행한다.
JWT를 서명이 아니라 암호화한다는 것은 비대칭 키로 서명했다면 서명한 키의 공개키를 들고 있는 당사자만 암호화된 토큰의 정보를 알 수 있다.
반대로 서명된 토큰은 누구든지 토큰을 소유하고 있다면 디코딩을 통해 토큰의 Claim 정보를 확인할 수 있다. 토큰의 Claim은 아래 JWT 구조에서 알아본다.
JWT 사용 예시
- Authorization(인가)
유저가 로그인 한 후에 발급받은 JWT를 다음 요청에 같이 보낸다면
해당 요청에 접근 권한 여부를 JWT에 담긴 내용을 토대로 결정할 수 있다.
- 인증 vs 인가
- 인증은 누군인지 판단하는 과정(ex: 아이디 비밀번호를 통한 로그인)
- 인가는 인증된 사용자의 자원 접근 여부를 판단하는 과정(ex: /user/profile)
- 정보 교환 JWT를 이용하면 안전하게 정보를 주고받을 수 있다.
여기서 ‘안전하게 정보를 주고받을 수 있다.’라는 문장은 잘못 받아들일 소지가 충분하기 때문에 잠깐 짚고 넘어가야 할 것 같다. 아래에서 자세하게 설명하겠지만, JWT는 크게 세 파트로 구성된다. [header, payload, signature] signature는 header와 payload를 이용해 서명 알고리즘으로 해당 토큰을 서명한다. 서명은 암호화와 다르기 때문에 해당 토큰을 누구든지 갖고 있다면 payload를 확인할 수 있기 때문에 암호화하지 않는 이상 우리가 전달하려는 정보는 결코 안전하지 않다. 따라서 JWT를 서명만 해서 사용한다면 민감한 정보를 안에 넣어서는 안된다.
그래서 서명된 JWT에서 ‘안전’하다는 의미는 바로 무결성이다.
위에서 signature는 header + payload + algorithm으로 서명했기 때문에 해당 signature를 다시 복호화 한다면 서명하기 전의 header와 payload를 다시 구할 수 있다.
그렇기 때문에 누군가 JWT의 내용(header, payload)를 변경한다면 서명했던 JWT SECRET KEY로 다시 복호화해서 서명하기 전의 header, payload와 전달받은 header, payload가 다름을 알 수 있다.
JWT 구조
JWT 구조는 세 파트로 구성되며, 각 파트틑 (.)로 구분된다.
- Header
- Payload
- Signature
따라서 JWT는 다음의 형식으로 구성된다.
- xxxx.yyyy.zzzz
header
- Header는 보통 두 파트로 구성된다.
- 토큰의 타입 : ex) JWT
- 사용된 알고리즘 : ex) HMAC SHA256 또는 RSA
{
"alg": "HS256",
"typ": "JWT"
}
- 이렇게 구성된 헤더는 Base64Url 방식으로 인코딩 된다.
Payload
JWT의 두 번째 파트는 Payload로, Payload는 Claims를 포함한다. Claims에는 보통 해당 JWT에 담고 싶은 정보들을 담는데, 가장 대표적인 예시는 로그인된 사용자의 정보를 담는다.
Payload는 세 가지 타입으로 나뉜다.
Registered claims(등록 클레임)
- Registered claims는 사전에 이미 정의된 Claims로 필수는 아니지만 사용을 권장한다고 한다.
- Registered claims의 예시로는 iss, exp, sub, aud and others
Public Claims(공개 클레임)
- 공개 클레임은 자유롭게 추가할 수 있지만, 자유롭게 정의가 가능하기 때문에 충돌이 발생할 수 있다.
- 충돌을 방지하려면 ‘IANA JSON Web Token Registry’에 등록하거나 충돌 방지 namespace를 포함한 URI로 정의하면 된다.
Private Claims(비공개 클레임)
- 비공개 클레임은 JWT를 주고받는 당자사 간에 주고받는 커스텀한 클레임으로 등록, 공개 클레임과는 다르다.
작성된 payload는 다음과 같다.
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
payload 역시 Base64Url로 인코딩된다.
위에서도 언급했지만, 서명된 토큰은 변조로부터는 안전하지만, 누구나 읽을 수 있기 때문에 민감한 정보는 저장하면 안된다고 강조하고 있다.!!
Signature
Signature 파트를 생성하기 위해서는 인코딩된 header, payload 그리고 secret key(header에 사용하기로 한 서명 알고리즘)가 필요하다.
HMAC SHA256 알고리즘으로 signature를 생성한다면 다음과 같이 만들어진다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Signature는 서명된 토큰의 변조를 확인할 수 있으며 개인키로 서명되었다면 해당 토큰의 발급자를 확인할 수도 있다.
완성된 JWT 토큰은 (.)로 각 파트를 구분하며, HTTP, HTML에서 쉽게 주고받을 수 있다.
JWT 동작 방식
만약 사용자가 정상적으로 로그인에 성공했다면, JWT를 리턴받는다.
발급받은 토큰은 사용자의 자격을 증명하기 위해 사용되기 때문에 보안에 신경써야 한다.
또한 필요 이상으로 해당 토큰을 오래 유지하면 안된다.
(일반적으로 토큰을 필요 이상으로 오래 유지하면 안된다.)
보통 access token과 refresh token을 발급해 access token의 만료 시간을 짧게 설정한다.
보안 상 민감한 데이터를 브라우저 storage에 저장하면 안된다.
일반적으로 서버를 구축하면 모두에게 접근을 허용하거나, 제한할 수 있다.
따라서 사용자는 특정 자원에 접근하고 싶다면 발급받은 JWT를 요청과 함께 전달해 서버에게 인가(허락)받아야 한다.
JWT를 전달하는 대표적인 방법은 HTTP Authorization 헤더에 Bearer 스키마를 사용하면 된다.
Authorization: Bearer <token>
특정 서버는 8KB 이상의 헤더는 받아들이지 않는 경우가 있기 때문에, JWT가 너무 커지지 않도록 관리해야 한다. 따라서 JWT에 담을 Claims을 신중하게 고려해서 담아야 한다.
Authroization 헤더에 토큰을 전송하면 Cross-Origin Resource Sharing는 쿠키를 사용하지 않기 때문에 문제가 발생하지 않는다.
JWT 사용 이유
JWT가 Simple Web Tokens(SWT), Security Assertions Markup Language와 비교해서 장점이 무엇이 있는지 알아보자.
- JSON은 XML에 비해 덜 복잡하다.
- JWT는 SMAL에 비해 더 작고 간결하다.
위의 이유들 때문에 HTTP와 HTML 환경에서 주고받기 더 편하다.(트래픽이 줄어든다.)
다음 시간에는 Spring Security의 구조와 동작 원리에 대해 알아보자.!! To be continued