OAuth 2.0 보안 강화: RFC 8705(mTLS)와 RFC 9449(DPoP), 그리고 Google의 적용 방식
1. Introduction
OAuth 2.0은 다양한 서비스에서 API 접근 권한을 위임하기 위한 표준 프로토콜이다. 그러나 기본적인 OAuth 2.0의 Access Token은 Bearer Token 방식으로 동작하기 때문에, 토큰이 탈취되면 공격자가 해당 토큰을 이용해 API를 호출할 수 있다는 보안 문제가 존재한다.
이러한 문제를 해결하기 위해 OAuth 2.0에서는 Access Token을 특정 클라이언트의 암호학적 키에 결합하는 Sender-Constrained Token 방식을 도입했다.
대표적인 표준이 다음 두 가지 RFC이다.
- RFC 8705: OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens
- RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP)
이 글에서는 두 표준의 동작 원리와 차이점, 그리고 Google OAuth에서의 적용 방식을 살펴본다.
2. 기존 OAuth 2.0의 문제점
일반적인 OAuth 2.0에서 Access Token은 Bearer Token이다.
GET /api/account HTTP/1.1
Host: api.example.com
Authorization: Bearer access_token
Bearer Token의 핵심 특성은 다음과 같다.
토큰을 소유한 누구나 해당 토큰을 사용할 수 있다.
따라서 Access Token이 탈취되면 공격자는 정상 클라이언트인 것처럼 API를 호출할 수 있다.
HTTPS는 전송 구간에서 토큰을 보호하지만, 클라이언트나 서버에서 토큰이 유출되는 상황까지 방지하지는 못한다.
이를 보완하기 위해 Sender-Constrained Token 방식이 등장했다.
Sender-Constrained Token
Sender-Constrained Token은 Access Token을 특정 암호학적 키에 바인딩하는 방식이다.
토큰을 사용하려면 다음 두 가지가 모두 필요하다.
- Access Token
- 해당 토큰에 바인딩된 키의 소유 증명
대표적인 방식이 mTLS와 DPoP이다.
3. RFC 8705: Mutual TLS (mTLS)
3.1 개요
RFC 8705는 OAuth 2.0에서 Mutual TLS를 활용하여 Client를 인증하고 Access Token을 인증서에 바인딩하는 방법을 정의한다.
RFC 8705의 주요 기능은 다음 두 가지다.
- OAuth 2.0 Mutual-TLS Client Authentication
- OAuth 2.0 Certificate-Bound Access Tokens
공식 문서:
https://www.rfc-editor.org/rfc/rfc8705.html
3.2 Mutual TLS란?
일반적인 TLS에서는 서버가 인증서를 제시하고 클라이언트가 서버를 인증한다.
반면 mTLS에서는 클라이언트도 인증서를 제시한다.
TLS Handshake
Client <----------------------------> Server
| |
| Client Certificate |
| Server Certificate |
| |
+---------- Mutual Authentication ------+
클라이언트는 인증서에 대응하는 Private Key를 소유하고 있음을 TLS Handshake 과정에서 증명한다.
따라서 서버는 단순히 클라이언트가 제시한 인증서를 확인하는 것이 아니라, 실제 Private Key 소유 여부까지 확인할 수 있다.
3.3 Certificate-Bound Access Token
RFC 8705의 핵심 기능 중 하나는 Access Token을 Client Certificate에 바인딩하는 것이다.
예를 들어 다음과 같은 관계가 만들어진다.
Client Certificate: Cert-A
Access Token: Token-A
Token-A -> Cert-A
Authorization Server는 Access Token 발급 시 인증서의 SHA-256 지문 등을 Token에 연결한다.
이후 Resource Server는 API 요청을 처리할 때 다음을 확인한다.
- Access Token이 유효한가?
- Access Token에 바인딩된 인증서와 현재 TLS 연결의 Client Certificate이 일치하는가?
인증서가 일치하지 않으면 요청을 거부한다.
3.4 API 요청 예시
GET /api/account HTTP/1.1
Host: api.example.com
Authorization: Bearer Token-A
HTTP 요청 자체는 기존 Bearer Token 방식과 동일하다.
차이점은 HTTP Header에 별도의 DPoP Proof를 추가하지 않는다는 점이다.
보안 검증은 TLS 연결에서 수행된다.
Client
|
| TLS Handshake + Client Certificate
|
| Authorization: Bearer Token-A
|
Resource Server
|
+-- Token 검증
+-- Certificate Binding 검증
+-- API 처리
3.5 RFC 8705의 특징
- TLS 기반 Client 인증
- 인증서 기반 Access Token 바인딩
- 별도의 Application-level Proof JWT 불필요
- 인증서 관리 필요(PKI 또는 Self-Signed 방식 선택 가능)
- 서버 간 통신 및 B2B 환경에 적합
주의할 점은 mTLS Client Authentication과 Certificate-Bound Access Token은 서로 독립적인 기능이라는 것이다.
mTLS로 Client를 인증한다고 해서 Access Token이 자동으로 인증서에 바인딩되는 것은 아니다.
4. RFC 9449: DPoP
4.1 개요
DPoP는 OAuth 2.0에서 Application Layer의 암호학적 Proof를 사용하여 Access Token을 특정 키에 바인딩하는 방식이다.
공식 문서:
https://www.rfc-editor.org/rfc/rfc9449.html
DPoP는 TLS Client Certificate 대신 Public Key와 Private Key를 이용한다.
Private Key -> DPoP Proof 서명
Public Key -> Access Token Binding
Client는 Private Key를 이용하여 DPoP Proof JWT를 생성하고 HTTP Header에 포함한다.
4.2 DPoP 동작 방식
DPoP는 다음과 같은 흐름으로 동작한다.
Client
|
| 1. Token Request + DPoP Proof
v
Authorization Server
|
| 2. Public Key에 Token Binding
v
Client
|
| 3. Access Token + DPoP Proof
v
Resource Server
|
| 4. Proof 검증
v
API Response
4.3 DPoP Proof JWT
DPoP Proof는 JWT 형식으로 생성된다.
Header 예시:
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}
여기서 jwk는 Client의 Public Key다.
Private Key는 JWT에 포함되지 않으며, 해당 키로 JWT에 서명한다.
Payload 예시:
{
"jti": "unique-id-123",
"htm": "GET",
"htu": "https://api.example.com/api/account",
"iat": 1760000000,
"ath": "access_token_hash"
}
주요 Claim의 의미는 다음과 같다.
| Claim | 의미 |
|---|---|
jti |
Proof의 고유 식별자 |
htm |
HTTP Method |
htu |
HTTP URI |
iat |
Proof 생성 시각 |
ath |
Access Token Hash |
nonce |
서버가 요구하는 경우 서버 제공 nonce |
4.4 Token 발급 요청
Client는 Authorization Server에 Token 발급을 요청하면서 DPoP Proof를 전달한다.
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
DPoP: eyJ...proof_jwt...
grant_type=authorization_code&
code=abc123&
client_id=my-client
Authorization Server는 DPoP Proof를 검증하고 Access Token을 해당 Public Key에 바인딩한다.
응답 예시:
{
"access_token": "eyJ...access_token...",
"token_type": "DPoP",
"expires_in": 3600
}
4.5 API 호출
Client는 API 호출 시 Access Token과 새로운 DPoP Proof를 함께 전달한다.
GET /api/account HTTP/1.1
Host: api.example.com
Authorization: DPoP eyJ...access_token...
DPoP: eyJ...new_proof_jwt...
두 값은 서로 다른 역할을 한다.
| JWT | 역할 |
|---|---|
| Access Token | API 접근 권한 |
| DPoP Proof | Private Key 소유 증명 및 요청 정보 증명 |
Resource Server는 DPoP Proof의 서명과 Claim을 검증한다.
주요 검증 항목은 다음과 같다.
- Proof의 서명이 올바른가?
- 실제 HTTP Method와
htm이 일치하는가? - 실제 요청 URI와
htu가 일치하는가? - Access Token Hash와
ath가 일치하는가? - Access Token에 바인딩된 Public Key와 Proof의 Public Key가 일치하는가?
- Proof가 재사용되거나 허용 시간 범위를 벗어나지 않았는가?
이 검증을 통해 Access Token만 탈취한 공격자는 API 호출을 수행할 수 없게 된다.
5. RFC 8705와 RFC 9449 비교
| 구분 | RFC 8705 (mTLS) | RFC 9449 (DPoP) |
|---|---|---|
| 보호 방식 | TLS Client Certificate | Application-level Proof |
| 키 종류 | X.509 Certificate + Private Key | Public/Private Key Pair |
| 소유 증명 | TLS Handshake | DPoP Proof JWT |
| Token Binding | Client Certificate | Public Key |
| 요청마다 Proof | TLS 연결에서 인증 | 매 HTTP 요청마다 Proof 생성 |
| HTTP Header | Bearer Token | DPoP Token + DPoP Proof |
| Client 인증 | 지원 | 자체적으로는 지원하지 않음 |
| 인증서 관리 | 필요 | 불필요 |
| Browser SPA | 사실상 사용 불가 | 상대적으로 적합 |
| Mobile App | 가능 | 가능 |
| B2B API | 적합 | 적합 |
두 방식 모두 Sender-Constrained Token을 구현하지만, 소유 증명 방식과 운영 모델이 다르다.
6. Google OAuth에서는 어떤 방식을 사용하는가?
Google OAuth를 이해할 때는 Google의 OAuth 기능과 RFC 표준을 구분해서 살펴볼 필요가 있다.
6.1 Google OAuth의 기본 구조
Google OAuth 2.0은 일반적인 OAuth 2.0 Authorization Code Flow를 지원한다.
주요 보안 기능은 다음과 같다.
- OAuth 2.0 Authorization Code Flow
- PKCE
- Client ID 기반 애플리케이션 식별
- Access Token 및 Refresh Token
- OAuth 앱 검증 및 동의 화면
- Token 관리 및 폐기
공식 문서:
https://developers.google.com/identity/protocols/oauth2
6.2 Google과 DPoP
Google은 DPoP를 지원하는 OAuth 기능을 제공한다.
다만 Google의 DPoP 적용은 RFC 9449의 일반적인 Access Token Sender-Constraining 방식과 차이가 있다.
Google의 DPoP 적용 방식에서는 다음과 같은 특징이 있다.
| 항목 | Google의 DPoP 적용 |
|---|---|
| DPoP | 지원 |
| Refresh Token Binding | 지원 |
| Refresh Token 갱신 | DPoP Proof 필요 |
| Access Token | Bearer Token 유지 |
| Google API 호출 | 기존 Bearer 방식 |
즉, Google은 DPoP를 통해 Refresh Token을 특정 키에 바인딩하고, Refresh Token 갱신 시 해당 키의 소유 증명을 요구한다.
이 방식은 장기간 사용되는 Refresh Token이 탈취되어 지속적인 토큰 발급에 악용되는 것을 방지하는 데 목적이 있다.
공식 문서:
https://developers.google.com/identity/protocols/oauth2/resources/dpop-adoption
6.3 Google과 mTLS
RFC 8705는 OAuth Client 인증과 Certificate-Bound Access Token을 위한 표준이다.
Google의 일반적인 OAuth 2.0 및 Google API 사용 흐름에서는 RFC 8705의 Certificate-Bound Access Token이 기본적인 Token Binding 방식으로 제공되는 것은 아니다.
따라서 Google OAuth에서 mTLS가 일반적으로 사용된다고 이해하는 것은 정확하지 않다.
7. 보안 관점에서 비교
| 공격 상황 | Bearer Token | mTLS | DPoP |
|---|---|---|---|
| Access Token 탈취 | API 호출 가능 | 인증서 Private Key 없으면 불가 | DPoP Private Key 없으면 불가 |
| Token 재사용 | 가능 | 인증서 Binding으로 제한 | Proof 검증으로 제한 |
| Client Private Key 탈취 | 해당 없음 | 악용 가능 | 악용 가능 |
| Client 자체 장악 | 악용 가능 | 악용 가능 | 악용 가능 |
두 방식 모두 Token만 탈취되는 상황에서 보안을 강화한다.
그러나 Client의 Private Key까지 탈취되거나 Client 자체가 장악되면 보안 효과가 감소한다.
DPoP는 Proof의 jti, iat, 서버 nonce 등을 활용하여 Replay 공격을 방지한다.
mTLS는 TLS Client Certificate과 Access Token을 결합하여 다른 Client의 Token 재사용을 방지한다.
8. 어떤 방식을 선택해야 하는가?
RFC 8705 (mTLS)가 적합한 경우
- 금융기관 및 B2B API
- 서버 간 통신
- Client Certificate 기반 인증 체계
- 인증서 발급 및 관리 체계가 이미 존재하는 환경
RFC 9449 (DPoP)가 적합한 경우
- Mobile Application
- Browser SPA
- 인증서 배포가 어려운 Client 환경
- Application Layer에서 Token Binding을 구현하고 싶은 경우
DPoP는 인증서 기반 TLS Client Authentication에 비해 Client 인증서 관리 부담이 적다는 장점이 있다.
9. Conclusion
RFC 8705와 RFC 9449는 OAuth 2.0의 Bearer Token이 가진 탈취 및 재사용 문제를 해결하기 위한 Sender-Constrained Token 표준이다.
핵심 차이는 다음과 같다.
- RFC 8705: TLS Client Certificate을 이용하여 Token에 바인딩된 Private Key의 소유를 증명한다.
- RFC 9449: Application Layer의 DPoP Proof JWT를 이용하여 Token 소유를 증명한다.
Google은 DPoP를 Refresh Token 보호에 활용하는 방식으로 적용하고 있으며, 일반적인 Google API 호출에서는 기존 Bearer Access Token 방식을 유지한다.
따라서 OAuth 보안 아키텍처를 설계할 때는 Token 자체의 유효성뿐 아니라 Token을 사용하는 Client의 소유 증명까지 고려하는 것이 중요하다.