지난 블로그 Cookie, Session, Token 에서 Token에 대해서 소개를 할 때 JWT에 대해서 간략하게 설명을 한 적이 있다. 하지만 이 JWT는 설명할 내용이 너무나도 많기 때문에 이번에 JWT에 대해서 조금 더 자세하게 알아보고자 한다!
정보를 비밀리에 전달하거나 인증할 때 주로 사용하는 토큰으로 JSON 객체를 이용함
JWT는 Json Web Token의 약자로 웹표준(RFC 7519)으로서 JSON 객체를 사용하여 가볍고 자가수용적인 방식으로 정보를 안정성 있게 전달해준다.
즉, JWT는 일반적으로 클라이언트와 서버 사이에서 통신할 때 권한을 위해 사용을 하는 Token이다.

JWT는 . 을 구분자로 3가지의 문자열로 되어있다. Header, Payload, Signature 세 파트로 나눠져 있으며 각각이 의미하는 것은 다음과 같다.
Header
{
"alg": "HS256",
"typ": "JWT"
}
여기서 typ 은 Token의 Type을 지정하는 것이며, JWT를 사용한다면 JWT라고 작성을 해주면 된다.
alg 은 암호화 알고리즘을 뜻한다. 주로 HS256, RS256 가 쓰인다.
💡 HS256, RS256이란?
우선 동일하게 있는 S256이란 SHA256 알고리즘을 뜻한다. SHA256 알고리즘은 데이터 무결성을 위해
사용되는 암호화 해쉬 알고리즘이다. 256비트로 구성되며 64자리 문자열을 반환한다.
HS256 = HMAC + SHA256
: 대칭키 암호화 알고리즘과 SHA256 알고리즘을 같이 사용하는 것이다.
RS256 = RSA + SHA256
: 비대칭키 암호화 알고리즘과 SHA256 알고리즘을 같이 사용하는 것이다.
대칭키와 비대칭키 암호화 방식에 대해서 궁금하다면 여기로!
Payload
JWT의 내용
Token에 담을 정보가 있음
정보를 담는 한 조각을 Claim이라고 함
💡 Claim이란?
key-value의 한 쌍으로 이루어진 정보를 담는 공간
하나의 Token에는 여러 개의 Claim이 들어갈 수 있다!
노출과 수정이 가능하기 때문에인증이 필요한 최소한의 정보만을 담는 것이 좋음
Claim의 종류에도 여러가지가 있다!
Registered Claims
Payload 부분에 들어갈 특별한 정보들이다. 이미 JWT 표준에 정의가 되어있는 것들이며, 일반적으로 널리 사용되는 정보들을 나타낸다.
{
"iss": "Token 발급자",
"sub": "Token 제목",
"aud": "Token 대상자",
"exp": "Token 만료시간, NumbericDate 형식(ex.1480849147370)",
"nbf": "해당 시간이 지나야 Token이 활성화, NumbericDate 형식",
"iat": "Token 발급시간",
"jti": "JWT 고유 식별자, 주로 중복적인 처리 방지에 사용"
}
위는 Registered Claim의 종류들이며, 모두 선택사항이다.
Public Claims
Payload에 들어가는 정보 중 누구나 사용할 수 있는 정보를 뜻한다. JWT 표준에 따라 정의가 되진 않았지만 필요에 따라 사용자가 임의로 추가할 수 있는 부분이다.
{
"https://profile.com/jwt_claims": true
}
만약 사용자의 프로필 사진 URL을 보내고 싶다면 위에처럼 URL을 작성하면 된다.
이 정보들은 보낸 사람과 받은 사람 사이에서 사용이 될 수 있다. 다만 표준에 따라 정의하는 것이 아니기 때문에 중복이 되지 않도록 유의해야하며, 추가적인 정보들만 넣어야한다. 보안과 관련된 민감한 사항을 작성할 경우 쉽게 정보에 노출될 수 있다..!
Private Claims
Payload에 들어가는 정보 중 특정한 사람들에게만 공유되어야 하는 비공개 정보를 뜻한다. JWT 표준에 따라 정의되지 않은 사용자 정의 정보이며, 민감한 정보를 포함할 때 유용하다.
{
"adminID": "개발자용",
"role": "admin"
}
위처럼 사용자의 ID나 권한 정보와 같은 민감한 정보들을 포함한다. 이런 민감한 정보의 경우 보안 조치를 취해서 안전한 전송과 저장을 보장할 수 있도록 해야한다!
Signature
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
"secret"
)
이처럼 Header 부분과 Payload 부분을 인코딩하고 합친 것을 서버의 secret key로 암호화 작업을 진행한다!
웃픈 이야기…!
JWT를 사용할 때 secret key는 어렵고 길게 작성을 해서 보안을 강화시켜야한다.
그러나 JWT 강의에서 secret key를 간단하게 “secret” 이라고 작성을 했었고, 이를 그대로 사용한 여러 회사들이 해커들에 의해 보안이 뚫린 적이 있다..!그외에도 해커들을 우습게 보고? 혹은 JWT가 너무 완벽하다고 생각해서? Secret Key를 너무 간단하게 작성한 경우 보안이 뚫릴 수 있으니 Secret Key는 무조건 길고 연상할 수 없는 임의의 난수로 지정을 해야한다!!
추가로 이러한 구조에 대해서 쉽게 알아볼 수 있는 사이트가 있다!
Header와 Payload, Signature를 작성해서 JWT로 인코딩도 해보고 다른 블로그에서 인코딩된 JWT를 디코딩을 통해 데이터를 확인해볼수도 있다!
JWT의 특징은 기존의 Session 기반 인증과 비교하여 설명할 수 있다!
Session 기반 인증이란 사용자의 인증 정보가 서버의 Session 저장소에 저장이 되는 방식으로
그에 반해 Token 기반 인증이란 인증 정보를 클라이언트가 직접 들고 있는 방식으로
이때 Session 방식에서는 서버가 직접 인증의 작업을 진행하기 때문에 서버를 확장하게 될 경우 한개의 Session 저장소가 아닌 각 서버마다 동일한 Session 저장소가 필요하게 되며, 여러 별도의 작업이 필요하다. 이는 확장성에 매우 불편하다. 또한, Session 저장소의 공간이 필요하기 때문에 서버에 부담이 된다..
그러나 Token 인증 방식에서는 클라이언트가 저장을 하고 Token의 유효성만 검증을 하면 되기 때문에 높은 확장성을 가진다. 그리고 별도의 저장공간이 필요없다.
이러한 특징 때문에 Session 인증 방식 대신에 Token 인증 방식을 사용을 하는 경우가 있다. 그리고 이 Token 인증 방식에서 사용되는 Token이 바로 JWT인 것이다.
이 JWT의 장점에 대해서 조금 더 살펴보자!
장점
그렇다면 단점은 없을까?
단점
JWT는 클라이언트가 저장을 하고 서버에 요청을 보낼 때 같이 보내준다고 하는데, 그럼 어떻게 같이 보내주는 것일까?
일반적으로 요청 메세지는 HTTP Method, Header, Body 이렇게 3 부분으로 이루어져 있다.
Body에는 요청을 보낼 때 필요한 데이터들을 넣을 수 있다.
여기서 우리가 중요하게 봐야할 것은 바로 Header이다!
HTTP 통신에서는 보호된 서버 리소스를 접근하는 클라이언트의 인증 정보를 확인하는 HTTP 인증 프레임워크가 있다.
Authorization: <type> <credentials>
위처럼 인증 Header에 요청을 사용하며 올바른 인증 정보를 넣으면 요청한 데이터를 받을 수 있다.
주로 사용을 하는 방식은 OAuth 2.0 프레임워크에서 사용하는 Token 인증 방식인 Bearer 인증 방식이다!
💡 OAuth 2.0?
인증을 위한 개방형 표준 프로토콜이며, 구글, 카카오, 네이버 등에서 제공하는
소셜 로그인 기능도 이 프로토콜을 기반으로 한 사용자 인증 기능을 제공한다.
Bearer는 소유자라는 뜻으로 해당 토큰의 소유자에게 권한을 부여해줘라는 의미로 이름을 붙였다고 한다!
Authorization: Bearer <token>
만일 토큰이 “aase.ewtat.3awe” 이라고 한다면, “Bearer aase.ewtat.3awe” 라고 작성을 해주면 된다!
주로 Java로 개발을 하기 때문에 Spring에서 JWT를 구현할 때 어떻게 해야할지에 관해서 얘기를 안할 수 없을 것 같다.
어떻게 구현하긴, 한줄한줄 구현하면 되지! 라고 생각할 수 있지만 그러기엔 이미 충분히 잘 나와있는 라이브러리들이 많다..!! 따라서 Java에는 어떤 라이브러리들이 있는지 살펴보고 해당 라이브러리를 사용하는 방법에 대해서 간단하게 설명을 해보겠다!
JJWT는 Java JWT라는 뜻으로 Java에서 JWT를 생성, 서명, 검증 및 디코딩하기 위한 라이브러리이다.
이 라이브러리가 제공하는 기능을 살펴보자!
JJWT 이외에 Java에서 사용하는 다른 JWT 라이브러리는 없을까? 물론! 다양하게 존재한다!
다른 JWT 라이브러리도 많은데 Spring에서 JWT를 구현하려고 할 때 많이 사용하는 라이브러리는 JJWT이다. 왜 JJWT를 사용하는 것일까?
이처럼 현재 Java에서는 JJWT가 가장 좋은 선택지로 여겨진다. 물론 이것은 취향의 차이이기 때문에 충분히 다른 라이브러리를 사용할 수 있다!
Spring Security는 인증과 권한 관리, 데이터 보호 기능을 포함하여 웹 개발에서 필수적인 사용자 관리 기능을 구현하는데 도움을 주는 Spring 프레임워크이다.
Spring Security는 라이브러리가 아닌데 왜 나왔는지 궁금한 사람들이 있을 것이다. 물론 Spring Security 없이도 JWT 기반 인증을 구현할 수 있다.
그렇다면 많은 사람들이 Spring Security를 사용 이유는 무엇일까?
물론 Spring Security를 꼭 사용해야하는 것은 아니다. 다음과 같은 보안 프레임워크들 역시 존재한다.
이렇게 대안이 있음에도 불구하고 Spring Security를 사용하는 이유는 Spring 기반 애플리케이션에서 매우 유용하며 통합된 보안 솔루션을 제공하여 많은 보안 관련 작업을 간소화할 수 있기 때문이다. 또한 많은 시간을 절약할 수 있다는 장점 역시 있다!
JWT는 매우 안전한 방식이 아니다. 여러 방식으로 문제가 생길 수 있고 이를 방지하기 위해서 고려해야할 부분들이 분명 존재한다. 하나씩 살펴보자!
JWT는 서버의 Secret Key를 사용해서 Signuatrue를 진행한다. 따라서 이 Secret Key의 관리는 매우 중요하다!
Secret Key 보안을 위해서는 다음과 같은 방식을 사용할 수 있다!
💡 KMS란?
Key Management Service의 약자로 애플리케이션의 중요한 데이터를 안전하게 보호하기 위해,
암호화 키를 간편하게 생성하고 안전하게 저장/관리하는 서비스
AWS KMS, Azure Key Valut 등 클라우드 서비스에서 제공Token이 탈취된다면 이를 막을 수 있는 방법이 거의 없다. 따라서 Token의 탈취된 경우를 방지하기 위해서 Token의 만료 시간을 짧게 설정하는 방법이 있다. 그렇게 된다면 Token이 탈취되어도 얼마 지나지 않아 무용지물이 되도록 만들 수 있기 때문이다.
이 만료 시간을 설정할 때 생각해야할 부분은 다음과 같다!
발급한 Token을 어디에 저장할 것인지에 따라서 생각해야할 부분이 각각 다르다!
💡 XSS 공격이란?
웹 사이트 관리자가 아닌 다른 사람이 웹 페이지에 악성 스크립트를 삽입하는 공격 방식위에서 Token이 탈취되었을 때를 방지하기 위한 방법으로 만료 시간을 짧게 설정하는 방식이 있다고 했다. 하지만 결국 탈취가 되었을 때 만료가 되기 전까지는 사용할 수 있다는 말이 된다. 따라서 이 Token 자체를 무효화하기 위한 전략도 생각을 해볼 필요가 있다!
Cross Site Request Forgery의 약자로 인증된 사용자가 웹 애플리케이션에 특정 요청을 보내도록 유도하는 공격행위를 뜻한다. 즉, 사이트 공격자의 요청이 사용자의 요청인 것처럼 속이는 공격 방식이다.
이 CSRF의 공격을 막기 위해서는 다음과 같은 방식을 생각할 수 있다!
JWT에서 가장 중요한 부분이라고 설명했던 Signature 부분 역시 보안상 매우 중요하다!
또한 Signature 말고도 Payload에 민감한 정보를 사용하기 위해서 암호화를 진행해야 하는 경우가 있을 것이다. 이때 다음과 같은 방식도 생각해볼 수 있다!
JWT의 Payload는 암호화가 되지 않기 때문에 이 Claim에는 민감한 정보를 포함시키지 않는 것이 좋다고 했다. 따라서 Claim에는 최소한의 정보만을 포함하는 것을 고려해봐야한다!
Signature로 검증을 하지만 Claim에 있는 정보들로 한번 더 확인을 하는 작업이 있다면 조금 더 보안에 신경을 써줄 수 있다!
단순 HTTP 전송을 하면 보안상 허점이 생길 수 있다. 때문에 HTTP에 Secure를 더한 HTTPS 전송을 사용하는 방법도 있다!
💡 MITM이란?
공격자가 비밀리에 도청을 하거나 이동하는 정보를 수정하기 위해서 두 당사자 사이에서 통신을 가로채는 공격 방식Token의 탈취를 방지하기 위한 다른 방법이라고 생각할 수 있다. Token을 재사용하는 것을 막아 탈취 당하더라도 문제가 생기지 않도록 할 수 있다!
이렇게 총 10개 정도의 보안 고려사항들에 대해서 알아보았다. 이러한 보안 고려사항을 철저히 준수하면 JWT를 사용하는 애플리케이션의 보안을 강화할 수 있다! 또한, 각 사항들은 서로 상호보완적이기 때문에 여러 가지 방법을 함께 사용하여 조금 더 심도 깊은 보안 전략을 구축하는 것이 좋다!
위의 보안 고려사항에서도 잠시 나왔지만 JWT에는 그와 관련된 기술들이 존재한다. 기술들이 무엇이 있는지 각각 어떤 역할을 하는지에 대해서 하나씩 살펴보자!
JWS는 JSON Web Signature의 약자로 데이터를 보호하기 위해 디지털 서명을 사용하여 JWT를 생성하는 방법을 정의한다. JWS를 사용하면 클라이언트와 서버 간에 전달될 때 Claim이 변조되지 않았는지 확인할 수 있다.
즉, 데이터의 무결성을 보장하기 위해 사용한다!
JWT와 뭐가 달라? 라고 할 수도 있다.
간단하게 얘기를 하자면 JWT는 JSON 형식의 Token 전체를 의미하지만 JWS는 JWT의 Signature 부분에 해당한다. JWS는 JWT의 서명된 형태인 것이다!
JWS는 데이터의 무결성을 보장하지만 데이터의 암호화를 포함하지 않기 때문에 민감한 정보를 Payload에 포함해서는 안된다!
JWE는 JSON Web Encryption의 약자로 데이터를 암호화하여 JWT를 생성하는 방법을 정의한다. JWT에는 암호화가 없지만 JWE는 암호화가 되어있어 Payload를 보호한다.
즉, 데이터의 기밀성을 유지하기 위해서 사용한다!
마찬가지로 JWT와 뭐가 달라? 라고 할 수 있다.
위에서도 얘기했지만 JWT는 내용이 암호화가 되지 않아 누구나 볼 수 있다. 그러나 JWE는 Token의 내용이 암호화되어 보호되며 매우 민감한 데이터를 전송할 때 사용이 된다.
그러나, 암호화 알고리즘에 따라 구현을 할 때 복잡성이 증가할 수 있다는 단점이 존재한다..!
JWE 구조를 살펴보면 다음과 같다!
이 모든 부분은 JWT와 마찬가지로 Base64Url로 인코딩되고, .으로 구분된다.
JWK란 JSON Web Key의 약자로 JSON 포맷으로 암호화 키를 표현하는 표준이다. 키는 공개 키 또는 비밀 키일 수 있으며, JSON 객체 내에 여러 속성을 포함하여 키의 메타데이터와 함께 전달된다.
즉, JWK는 키를 표현하는 형식이다!
이번에도 역시 JWT와 무엇이 다른지 살펴보자.
JWT는 정보를 전송하기 위한 Token이고, JWK는 JWT를 서명하거나 암호화하기 위해 사용되는 키를 표현하는 형식이다! 때문에 JWK는 주로 키 관리와 교환에 사용된다.
JWK 구조를 살펴보면 다음과 같다!
RSA, EC 등sig - 서명, enc - 암호화RS256, ES256 등위에서도 설명을 했지만 다시 한 번 정리하면서 어떻게 적용하는지 살펴보자!
JWT는 정보를 JSON 객체로 표현하여 인코딩된 Token이다. 정보 전달을 위해서 사용이 된다!
JWS는 JWT에 서명을 추가하여 무결성을 보장한다!
JWE는 JWT에 암호화를 하여 기밀성을 보장한다!
JWK는 암호화 키를 표현하여 키 관리를 용이하게 한다!
JWT는 정보를 비밀리에 전달하거나 인증할 때 주로 사용하는 토큰으로 JSON 객체를 이용한다!
JWT와 다른 보안방식을 같이 사용하게 된다면 조금 더 안전하게 사용할 수 있다!