참여하고 있는 스터디에서 위 주제로 간단한 세션 발표를 진행하게 되어, 관련하여 글을 준비하게 되었다.😵
글의 목표는 다음과 같다.
- JWT 서명(Signature)에 대해 간략히 리뷰한다.
- HMAC / RSA 알고리즘을 사용해 JWT 서명을 생성하고 검증하는 과정을 이해한다.
- 분산 환경에서 JWT를 어떻게 검증할 수 있을지 알아본다.
1. JWT Signature
- JWT의 세 번째 구성 요소
- Base64 URL 인코딩 된 Header + Payload를 secret key로 암호화 한 값
- 무결성: Payload의 위조 여부 확인
- 신뢰성: 누가 보냈는지 인증 가능
2. JWT Signature 생성
- HS256
1. 개념
- HS256은 HMAC 방식 + SHA-256 해시 함수를 사용한 알고리즘
- HMAC (Hash-based Message Authentication Code)
- JWT의 위변조 되었는지 검증하기 위해, 비밀키를 해싱 과정에 추가
- 독립적인 암호화 알고리즘이라기보다는 해시 알고리즘을 확장한 알고리즘
- SHA256 (Secure Hash Alogrithm 256bit)
- 입력값을 32 byte 고정된 길이의 해시값으로 변환하는 단방향 해시 알고리즘
- HMAC (Hash-based Message Authentication Code)
- 즉, 데이터 암호화가 아닌 데이터의 무결성을 확인하는 알고리즘이다.
2. 서명 생성 (송신자)
- Base64 URL 인코딩 한 Header와 Payload를 입력값으로, 비밀키(my-secret-key)를 대칭키로 활용해 HMAC-SHA256 해시 값을 계산한다.
- 입력: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0
- 비밀 키: my-secret-key
- 결과: HMAC-SHA256 해시 값
- 결과 해시 값을 Base64 URL 인코딩을 하면 JWT 서명(Signature)이 만들어진다.
- 이를 조합한 JWT는 다음과 같다.
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Base64UrlEncode(해시 값)
3. 서명 검증 (수신자)
- 송신자와 같은 방식으로 JWT의 Header와 Payload를 결합
- 동일한 비밀 키(my-secret-key)를 사용해 HMAC-SHA256 알고리즘으로 해시 값을 계산
- 계산된 해시 값과 JWT Signature와 일치하면 유효한 토큰으로 인정
- RS256
1. 개념
- RS256은 RSA 비대칭키 서명 + SHA-256 해시 함수를 사용한 서명 알고리즘이다.
- RSA
- 공개키와 개인키 쌍을 사용하는 비대칭키 암호화 알고리즘
- SHA256 (Secure Hash Alogrithm 256bit)
- 입력 데이터를 32 byte 고정된 길이의 해시값으로 변환하는 단방향 해시 알고리즘
- RSA
2. 서명 생성 (송신자)
- Base64 URL 인코딩 한 Header와 Payload를 입력값으로, 개인키를 사용해 RSA-SHA256 알고리즘으로 해시 값을 계산
- 입력: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0
- 비밀 키: private-key
- 결과: SHA256 해시 값
- 이 해시 값을 다시 Base64 URL 인코딩을 하면 JWT 서명(Signature)이 만들어진다.
3. 서명 검증 (수신자)
- 신자와 같은 방식으로 JWT의 Header와 Payload를 결합
- 송신자가 제공한 공개키를 사용해 RSA-SHA256 알고리즘으로 서명을 복호화하여 해시 값 계산
- 계산된 해시 값과 JWT Signature와 일치하면 유효한 토큰으로 인정
3. 분산 환경에서의 JWT 검증
하나의 단일 서버 환경에서는 문제 없이 JWT를 검증할 수 있다.
하지만 분산 환경에서는 JWT를 발급하는 주체와 검증하는 주체가 서로 다른 경우가 생긴다.
이 때, JWT의 서명을 안전하게 검증할 수 있는 구조가 필요하다.
- 대칭키 방식
1. 동작 원리
- 모든 서버가 동일한 대칭키(secret key)를 공유하여 JWT를 생성하고 검증한다.
2. 분산 환경에서의 문제점
- 모든 서버에 동일한 secret key를 배포해야 함
- 키 유출 시, 위조된 JWT 생성 가능
- 키를 변경하는 것이 어려움
- 비대칭키 방식
1. 동작 원리
- 인증 서버는 개인키로 JWT를 생성한다.
- 검증 서버들은 공개키만으로 검증한다.
2. 분산 환경에서의 이점
- 공개키는 안전하게 공유 가능 → 검증 서버가 많아도 문제 없음
- 개인키는 하나의 발급 서버에만 보관
- 키 교체도 안전하게 진행 가능 (JWK + kid)
3. JWKS & 키 교체 (Key Rotation)
- 인증 서버는 공개키를 JWKS(JSON Web Key Set) 형태로 공개한다.
- https://your-auth-server.com/.well-known/jwks.json 와 같은 URL로 공개
- 검증 서버는 JWK Set에서 JWT의 kid 필드를 참조해, JWKS에서 해당 키를 찾아 서명 검증한다.
- 이를 통해 비대칭키 교체를 유연하게 할 수 있다.
// 새로운 JWT 시 rsa-key-2 개인키 사용
{
"alg": "HS256",
"typ": "JWT",
"kid": "rsa-key-2"
}
// JWK (Json Web Key)
{
"keys": [
{
"kty": "RSA",
"kid": "rsa-key-1",
"alg": "RS256",
"n": "...",
"e": "AQAB"
},
{
"kty": "RSA",
"kid": "rsa-key-2",
"alg": "RS256",
"n": "...",
"e": "AQAB"
}
]
}
4. 그럼 어떤 방식을 선택할 수 있을까?
단일 서버 환경에서는 대칭키 기반의 HS256 알고리즘을 사용하는 것이 충분하다.
하나의 서버가 JWT를 생성하고 검증까지 담당하므로, 비밀 키를 외부에 노출하거나 여러 서버 간 키를 공유할 필요가 없기 때문이다.
그러나 서버가 여러 대로 구성된 분산 환경에서는 모든 서버에 동일한 비밀 키를 배포해야 되고, 이 과정에서 키가 유출될 경우 보안 사고로 이어질 수 있다. 또한 비밀 키를 교체 시, 교체된 비밀 키를 배포하는 과정에서 다시 유출 위험에 노출된다.
하지만 비대칭키 기반의 RS256 알고리즘을 사용하면, JWT 생성은 개인키로 수행하고 검증은 공개키만으로 할 수 있어 키 관리가 매우 편해진다. 공개키는 검증 서버에 안전하게 배포할 수 있고, 개인키는 오직 인증 서버만 보관하면 된다. 또한 JWKS를 통해 공개키를 인증 서버에서 통합적으로 관리하고, kid를 이용해 키 교체도 유연하게 할 수 있다.
결론적으로,
- 단일 서버 → HS256으로 간편하게 구성하고
- 분산 서버 → RS256 + JWKS로 안정성과 확장성 확보하자!