Backend & Spring (스프링)/Authorization & Authentication

OAuth 2.0 & OIDC with JWT

jw92 2023. 11. 29. 21:23

 Authorization (허가) Authentication (인증) 총 정리 - MAC AEAD 에 이어지는 글

9. JWT (JSON Web Tokens)

JWT는 Token 기반 인증에서 RFC 7519 의 표준으로 지정된 Token
 
※ Claim이란 어떠한 주제에 관해 주장하는 정보이며, name/value pair로 표시한다.
 

9-1. Javascript Object Signing and Encryption (JOSE)

Claim을 안전하게 전송하기 위한 프레임워크
JWK, JWS, JWE, JWT 등을 포함한다.
 

9-2. JWK(Json Web Key): 암호화 키를 나타내는 Json Data 구조

jwk = {'k': <password>}

위와 같이 표현되며, k가 key 혹은 password를 나타낸다.
 

9-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가 없다.
 

9-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를 모두 지킨다.
 

9-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을 만들어 볼 수 있다.
 

10. OAuth 2.0 (Open Authorization)

OAuth는 Access Delegation (액세스 위임)을 위한 개방형 표준으로,
일반적으로 인터넷 사용자가 암호를 제공하지 않고
웹사이트나 애플리케이션에 다른 웹사이트의 정보에 대한 액세스 권한을 부여하는 방법으로 사용한다.
 
Amazon, Google, Facebook, Microsoft 및 Twitter와 같은 회사에서 사용자가 자신의 계정에 대한 정보를 제3자 응용 프로그램이나 웹 사이트와 공유할 수 있도록 허용하는 데 사용된다.

OAuth가 적용되지 않는 세계에서 Third-party Application이 Google Drive에 사진을 업로드하고 싶다.
어떻게 해야될까?
Google Account의 아이디(email)와 비밀번호를 Third-party Application에 제공해야 한다.
 
사용자가 이러한 앱을 쓰려고 할까?
 
이러한 문제를 해결하기 위해 나온 것이 OAuth이다.

OAuth 프로토콜은 Resource Owner(사용자)가 Client (Third-party app)에 Server Resournce를 접근할 수 있는 secure delegated access(보안 위임 액세스) 방법을 제공한다.
 

Resource Owner가 ID/Password같은 credentials을 제공하지 않고도
Server Resource에 대한  third-party access를 승인할 수 있게 해준다.
이 OAuth는 HTTP를 기반으로 설계 되었으며,
Authorization Server가 Resource Owner에게 승인받아 Client에게 Access Token을 발급해주는 것을 허용한다.
Client는 이 Access Token을 사용하여 Resource Server의 protected resources에 접근한다.

 

11. OIDC (OpenID Connection)

OAuth는 Authorization을 하기 위한 Protocol이며, 권한 부여를 위한 Access Token을 발급해준다.

OIDC는 OAuth 2.0을 기반으로 만들어져서 OAuth  중 ID 계층을 담당한다.
Scope를 openid로 지정하면 Authorization Server는 Access Token과 함께 사용자 인증에 필요한 정보를 ID Token으로 같이 발급해주는 것이다.
 

Login with ... 
 

OAuth만 가지고도 인증할 수 있잖아??

맞다. 하지만 OAuth만으로 인증하기 위해서는
Authorization Server로부터 Access Token을 발급받아 이를 Resource Server에 다시 요청해야한다.

OIDC를 지원한다면 통신량을 절반으로 줄일 수 있는 것이다.
사용자 관점에서는 OAuth를 쓰나 OIDC를 쓰나 변화는 없을 것 같다

12. SAML (Security Assertion Markup Language)

하나의 자격 증명으로 한 번만 로그인하여 여러 앱에 액세스할 수 있도록 해 주는 기술
사용자가 로그인하면 ID 공급자가 사용자를 확인한 후 사용자가 액세스하려는 사이트, 서비스 또는 앱의 서비스 공급자 측으로 인증 데이터를 전달한다.
 
OIDC와 비슷하나 OAuth 기반이 아니며 JWT가 아닌 XML SAML을 이용하여 인증.
 

13. SSO (Single Sign On)

SAML / OAuth / OIDC 등을 이용하여 한 번의 로그인으로 여러가지 어플리케이션을 이용가능하게 해주는 것