JWT는 Token 기반 인증에서 RFC 7519 의 표준으로 지정된 Token
※ Claim이란 어떠한 주제에 관해 주장하는 정보이며, name/value pair로 표시한다.
1. Javascript Object Signing and Encryption (JOSE)
Claim을 안전하게 전송하기 위한 프레임워크
JWK, JWS, JWE, JWT 등을 포함한다.
2. JWK(Json Web Key): 암호화 키를 나타내는 Json Data 구조
jwk = {'k': <password>}
위와 같이 표현되며, k가 key 혹은 password를 나타낸다.
3. JWS(Json Web Signature): Set of claims에 signature(서명)을 하여 Base64Url로 Encoding
import jose
claims = {
'iss': 'http://www.example.com',
'exp': int(time()) + 3600,
'sub': 42,
}
jwk = {'k': 'password'}
jws = jose.sign(claims, jwk, alg='HS256')
예를 들면 위와같이 jws를 구성할 수 있고, 아래와 같은 JWS가 만들어진다.
JWS(header='eyJhbGciOiAiSFMyNTYifQ',
payload='eyJpc3MiOiAiaHR0cDovL3d3dy5leGFtcGxlLmNvbSIsICJzdWIiOiA0MiwgImV4cCI6IDEzOTU2NzQ0Mjd9',
signature='WYApAiwiKd-eDClA1fg7XFrnfHzUTgrmdRQY4M19Vr8')
이 때, 서명에는 Digital Signature 혹은 MAC 이 사용된다.
header에는 다음과 같은 signature 알고리즘 정보가 있다.
{"alg": "HS256"}
Signature를 하기 때문에 Integrity가 있으나 payload를 누구나 볼 수 있어 Confidentiality가 없다.
4. JWE(Json Web Encryption): Set of claims를 암호화.
import jose
from time import time
from Crypto.PublicKey import RSA
# key for demonstration purposes
key = RSA.generate(2048)
claims = {
'iss': 'http://www.example.com',
'exp': int(time()) + 3600,
'sub': 42,
}
# encrypt claims using the public key
pub_jwk = {'k': key.publickey().exportKey('PEM')}
jwe = jose.encrypt(claims, pub_jwk)
위와 같이 JWE를 만들 수 있으며, 아래와 같은 JWE가 만들어진다.
JWE(header='eyJhbGciOiAiUlNBLU9BRVAiLCAiZW5jIjogIkExMjhDQkMtSFMyNTYifQ',
cek='SsLgP2bNKYDYGzHvLYY7rsVEBHSms6_jW-WfglHqD9giJhWwrOwqLZOaoOycsf_EBJCkHq9-vbxRb7WiNdy_C9J0_RnRRBGII6z_G4bVb18bkbJMeZMV6vpUut_iuRWoct_weg_VZ3iR2xMbl-yE8Hnc63pAGJcIwngfZ3sMX8rBeni_koxCc88LhioP8zRQxNkoNpvw-kTCz0xv6SU_zL8p79_-_2zilVyMt76Pc7WV46iI3EWIvP6SG04sguaTzrDXCLp6ykLGaXB7NRFJ5PJ9Lmh5yinAJzCdWQ-4XKKkNPorSiVmRiRSQ4z0S2eo2LtvqJhXCrghKpBNgbtnJQ',
iv='Awelp3ryBVpdFhRckQ-KKw',
ciphertext='1MyZ-3nky1EFO4UgTB-9C2EHpYh1Z-ij0RbiuuMez70nIH7uqL9hlhskutO0oPjqdpmNc9glSmO9pheMH2DVag',
tag='Xccck85XZMvG-fAJ6oDnAw')
이를 Decryption 해보면 아래와 같이 저장되어 있는 것을 확인할 수 있다.
JWT(header={u'alg': u'RSA-OAEP', u'enc': u'A128CBC-HS256'},
claims={u'iss': u'http://www.example.com', u'sub': 42, u'exp': 1395606273})
JWE에는 2가지의 Key가 사용된다.
- CEK(Content Encryption Key) Content를 암호화하기 위한 Key, AEAD 알고리즘을 위한 Symmetric Key가 사용된다.
- Encryption Key: CEK를 암호화하기 위한 Encryption Key. 암호화된 CEK는 JWE Encrypted Key로 부른다.
Asymmetric Key(비대칭 키)를 사용한다.
송신자 측에서는 Public Key를 가지고 있으므로 CEK를 암호화할 수 있고,
이를 Header에 포함해서 보내면
수신자 측에서 Private Key를 이용해서 CEK를 복호화할 수 있고,
이를 이용해서 Content를 확인할 수 있다.
암호화 알고리즘을 파악하기 위해 Header에 아래와 같은 정보를 포함해서 보낸다.
{"alg":"RSA-OAEP","enc":"A256GCM"}
JWE만을 사용하면 Confidentiality는 지켜지나 Integrity가 없다.
이를 해결하기 위하여 AEAD를 사용하여 Integrity와 Confidentiality를 모두 지킨다.
5. JWT (Json Web Tokens)
권한에 관련한 Claim 집합을 JWS 혹은(or + and) JWE 구조로 Base64Url 으로 Encoding하여
JWS Compact Serialization 혹은 the JWE Compact Serialization 으로 표현한 것이다.
JWS to JWT
jwt = jose.serialize_compact(jws)
# 'eyJhbGciOiAiSFMyNTYifQ.eyJpc3MiOiAiaHR0cDovL3d3dy5leGFtcGxlLmNvbSIsICJzdWIiOiA0MiwgImV4cCI6IDEzOTU2NzQ0Mjd9.WYApAiwiKd-eDClA1fg7XFrnfHzUTgrmdRQY4M19Vr8'
jose.verify(jose.deserialize_compact(jwt), jwk, 'HS256')
# JWT(header={u'alg': u'HS256'}, claims={u'iss': u'http://www.example.com', u'sub': 42, u'exp': 1395674427})
JWE to JWT
jwt = jose.serialize_compact(jwe)
# 'eyJhbGciOiAiUlNBLU9BRVAiLCAiZW5jIjogIkExMjhDQkMtSFMyNTYifQ.SsLgP2bNKYDYGzHvLYY7rsVEBHSms6_jW-WfglHqD9giJhWwrOwqLZOaoOycsf_EBJCkHq9-vbxRb7WiNdy_C9J0_RnRRBGII6z_G4bVb18bkbJMeZMV6vpUut_iuRWoct_weg_VZ3iR2xMbl-yE8Hnc63pAGJcIwngfZ3sMX8rBeni_koxCc88LhioP8zRQxNkoNpvw-kTCz0xv6SU_zL8p79_-_2zilVyMt76Pc7WV46iI3EWIvP6SG04sguaTzrDXCLp6ykLGaXB7NRFJ5PJ9Lmh5yinAJzCdWQ-4XKKkNPorSiVmRiRSQ4z0S2eo2LtvqJhXCrghKpBNgbtnJQ.Awelp3ryBVpdFhRckQ-KKw.1MyZ-3nky1EFO4UgTB-9C2EHpYh1Z-ij0RbiuuMez70nIH7uqL9hlhskutO0oPjqdpmNc9glSmO9pheMH2DVag.Xccck85XZMvG-fAJ6oDnAw'
# decrypt on the other end using the private key
priv_jwk = {'k': key.exportKey('PEM')}
jwt = jose.decrypt(jose.deserialize_compact(jwt), priv_jwk)
# JWT(header={u'alg': u'RSA-OAEP', u'enc': u'A128CBC-HS256'},
# claims={u'iss': u'http://www.example.com', u'sub': 42, u'exp': 1395606273})
일반적인 토큰 인증 환경에서는 TLS 등을 통하여 Confidentiality가 지켜지기 때문에
일반적으로 JWS만을 사용하고, 필요 시 JWS + JWE 를 모두 사용한다.
즉, 일반적으로 JWT는 JWS와 JWS Compact Serialization을 의미한다.
따라서 JWT는 아래 그림과 같이 3 부분으로 구성된다.
1. Header를 Base64Url로 Encoding한 부분
2. Payload를 Encoding한 부분
3. Signature를 Encoding한 부분
JWE Compact Serialization은 아래와 같이 5 부분으로 구성된다.
BASE64URL(UTF8(JWE Protected Header)) || '.' ||
BASE64URL(JWE Encrypted Key) || '.' ||
BASE64URL(JWE Initialization Vector) || '.' ||
BASE64URL(JWE Ciphertext) || '.' ||
BASE64URL(JWE Authentication Tag)
https://jwt.io/ 에서 간단하게 실제 jwt token을 만들어 볼 수 있다.
2023.11.29 - [Backend & Spring (스프링)/Authorization & Authentication] - OAuth 2.0 & OIDC with JWT
'Backend & Spring (스프링) > Authorization & Authentication' 카테고리의 다른 글
OAuth 2.0 & OIDC with JWT (0) | 2023.11.29 |
---|---|
Authorization (허가) Authentication (인증) 총 정리 - MAC AEAD (1) | 2023.11.28 |