OAuth 2.0 액세스 토큰은 API 왕국의 열쇠예요. 누군가 토큰을 훔치면 어디서든 사용할 수 있어요. RFC 9449에 정의된 DPoP(Demonstrating Proof of Possession)는 토큰을 요청한 클라이언트에 암호학적으로 바인딩해서 이 문제를 해결해요. 토큰이 가로채지더라도 클라이언트의 개인 키 없이는 쓸모없어요.
sgcWebSockets 2026.4.0부터 OAuth2 클라이언트와 서버 컴포넌트에 완전한 DPoP 지원이 포함돼요. 이 글은 DPoP가 무엇인지, 어떻게 작동하는지, Delphi 애플리케이션에서 어떻게 설정하는지 설명해요.
DPoP란 무엇인가요?
표준 OAuth 2.0에서 Bearer 토큰은 현금처럼 작동해요. 토큰을 가진 사람이면 누구든 사용할 수 있어요. 공격자가 손상된 네트워크, 유출된 로그, 악성 프록시를 통해 토큰을 가로채면 보호된 리소스에 완전한 접근 권한을 얻어요.
DPoP는 토큰이 PIN이 있는 신용카드처럼 작동하도록 변경해요. 토큰 자체가 공개 키에 바인딩되고, 모든 요청에 해당 개인 키로 서명된 증명 JWT가 포함돼야 해요. 서버는 요청을 수락하기 전에 증명이 바인딩된 키와 일치하는지 검증해요.
|
Bearer 토큰 (전통적 방식) 토큰을 가진 사람이라면 누구든 사용할 수 있어요. 훔친 토큰은 완전히 악용 가능해요. 신원 증명이 필요 없어요. |
DPoP 토큰 (발신자 제한) 토큰이 키 쌍에 바인딩돼요. 모든 요청에 새로운 서명 증명이 필요해요. 개인 키 없이는 훔친 토큰이 쓸모없어요. |
DPoP 작동 방식
DPoP 흐름은 표준 OAuth2 프로세스에 경량 암호화 단계를 추가해요:
1. 키 생성 — 클라이언트가 비대칭 키 쌍(ES256 또는 RS256)을 생성해요. 개인 키는 클라이언트에 유지되고, 공개 키는 DPoP 증명에 JWK로 포함돼요.
2. 토큰 요청 — 액세스 토큰을 요청할 때 클라이언트는 서명된 JWT 증명이 포함된 DPoP HTTP 헤더를 포함해요. 증명에는 HTTP 메서드, URL, 타임스탬프, 고유 식별자가 포함돼요.
3. 토큰 바인딩 — 권한 부여 서버가 증명 서명을 검증하고, JWK 썸프린트(공개 키의 SHA-256 해시)를 추출해 발급된 토큰에 바인딩해요. 응답에는 Bearer 대신 token_type: DPoP가 포함돼요.
4. 리소스 접근 — 모든 API 호출에서 클라이언트는 토큰과 새로운 DPoP 증명을 함께 전송해요. 증명에는 ath 클레임(액세스 토큰의 SHA-256 해시)이 포함되어 증명을 해당 특정 토큰에 바인딩해요.
DPoP 증명 내부
DPoP 증명은 헤더, 페이로드, 서명의 세 부분으로 구성된 컴팩트 JWT예요.
// Header - identifies the key and algorithm
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "cWs37kZLJMej6fpd...",
"y": "e2bkcQGaBERgSZUb..."
}
}
// Payload - proves freshness and binds to the request
{
"htm": "POST",
"htu": "https://auth.example.com/oauth/token",
"iat": 1774950263,
"jti": "F1AFCD1F-95F7-401B-A2F5-195A31DB1802",
"ath": "fUHyO2r2Z3DZ53EsNr..."
}
sgcWebSockets에서 DPoP 설정하기
1단계: ES256 키 쌍 생성
OpenSSL을 사용해 타원 곡선 키 쌍을 생성하세요:
# Generate EC private key
openssl ecparam -name prime256v1 -genkey -noout -out dpop_private.pem
# Extract public key parameters
openssl ec -in dpop_private.pem -text -noout
공개 키 X와 Y 좌표를 Base64URL로 변환해 JWK를 만드세요:
{"kty":"EC","crv":"P-256","x":"cWs37kZLJMej6fpdyKaI8Gz6CE...","y":"e2bkcQGaBERgSZUbAGR-iOOM..."}
2단계: OAuth2 클라이언트 설정
// Configure OAuth2 as usual
OAuth2.OAuth2Options.GrantType := auth2CodePKCE;
OAuth2.OAuth2Options.ClientId := 'your-client-id';
OAuth2.OAuth2Options.ClientSecret := 'your-client-secret';
OAuth2.AuthorizationServerOptions.AuthURL := 'https://auth.example.com/authorize';
OAuth2.AuthorizationServerOptions.TokenURL := 'https://auth.example.com/oauth/token';
// Enable DPoP
OAuth2.DPoPOptions.Enabled := True;
OAuth2.DPoPOptions.Algorithm := dpopES256;
OAuth2.DPoPOptions.PrivateKey.LoadFromFile('dpop_private.pem');
OAuth2.DPoPOptions.PublicKeyJWK := '{"kty":"EC","crv":"P-256","x":"...","y":"..."}';
// Start the OAuth2 flow - DPoP headers are added automatically
OAuth2.Start;
이게 전부예요. 컴포넌트가 자동으로 다음을 수행해요:
- 모든 토큰 요청에 대해 새로운 DPoP 증명 JWT를 생성해요
DPoP-Nonce서버 챌린지를 투명하게 처리하고 재시도해요- 이후 요청을 위해 nonce를 저장해요
3단계: API 호출에 DPoP 증명 사용
DPoP 바인딩된 액세스 토큰을 얻은 후 각 API 요청에 대한 증명을 생성하세요:
var
vProof: String;
begin
// Generate a DPoP proof for the API call
vProof := OAuth2.GetDPoPProof(
'GET',
'https://api.example.com/userinfo',
OAuth2.AccessToken
);
// Include both headers in your HTTP request:
// Authorization: DPoP <access_token>
// DPoP: <proof_jwt>
HTTPClient.Request.CustomHeaders.AddValue('Authorization',
'DPoP ' + OAuth2.AccessToken);
HTTPClient.Request.CustomHeaders.AddValue('DPoP', vProof);
HTTPClient.Get('https://api.example.com/userinfo');
end;
지원 공급자
DPoP는 이미 주요 OAuth2 공급자에서 지원돼요:
| Auth0 | 애플리케이션 설정에서 DPoP를 활성화하세요. nonce 지원이 필요해요(자동으로 처리됨). |
| Okta | 권한 부여 서버 접근 정책에서 DPoP를 설정하세요. 2024년부터 GA예요. |
| Microsoft Entra ID | 기밀 클라이언트에 대한 DPoP를 지원해요. |
| Ping Identity | PingOne 및 PingFederate에서 완전한 DPoP 지원을 제공해요. |
서버 측 DPoP 검증
sgcWebSockets OAuth2 서버 컴포넌트도 DPoP 검증을 지원해요. OAuth2Options.DPoP가 활성화되면 서버가 자동으로 다음을 수행해요:
- DPoP 증명 JWT를 검증해요(서명, 클레임, 최신성)
- 토큰을 클라이언트의 JWK 썸프린트에 바인딩해요
- DPoP 바인딩된 토큰에 대해
token_type: DPoP를 반환해요 - 유효하지 않거나 누락된 증명이 있는 요청을 거부해요(400
invalid_dpop_proof반환) - 리소스 요청에서
ath클레임(액세스 토큰 해시)을 검증해요
// Enable DPoP on the OAuth2 server
OAuth2Server.OAuth2Options.DPoP := True;
// Optional: custom validation via event
OAuth2Server.OnOAuth2ValidateDPoP := procedure(Sender: TObject;
Connection: TsgcWSConnection;
const DPoPProof, AccessToken: String;
var IsValid: Boolean)
begin
// Add custom checks here (e.g., verify against a key registry)
IsValid := True;
end;
DPoP API 참조
| 속성 / 메서드 | 설명 |
|---|---|
DPoPOptions.Enabled |
모든 토큰 요청에 DPoP를 활성화해요. |
DPoPOptions.Algorithm |
dpopES256(권장) 또는 dpopRS256. |
DPoPOptions.PrivateKey |
DPoP 증명 서명에 사용하는 PEM 인코딩 개인 키. |
DPoPOptions.PublicKeyJWK |
공개 키의 JSON Web Key 표현. |
GetDPoPProof() |
특정 HTTP 메서드, URL, 액세스 토큰에 대한 DPoP 증명 JWT를 생성해요. |
GetDPoPJWKThumbprint() |
공개 키의 RFC 7638 SHA-256 썸프린트를 반환해요. |
DPoPNonce |
서버로부터 받은 현재 DPoP-Nonce 값(읽기 전용). |
토큰 보안 강화하기
DPoP는 PKCE 이후 OAuth 2.0 토큰 보안에서 가장 중요한 개선 사항이에요. 최소한의 코드 변경으로 토큰 도용 공격 전체를 제거해요. DPoPOptions.Enabled := True로 설정하고 키를 제공하면 sgcWebSockets 컴포넌트가 나머지를 처리해요.
DPoP 지원은 Delphi(DXE6~D13)와 .NET(.NET Framework 2.0~.NET 9) 모두를 위한 sgcWebSockets 2026.4.0에서 사용할 수 있어요. esegece.com에서 최신 버전을 다운로드하세요.
