빠른 답변: OAuth 1.0a는 복잡한 암호화 요청 서명에 의존했고 OAuth 2.0은 재전송 공격에 취약한 Bearer 토큰을 도입한 반면, OAuth 2.1은 자율형 AI 에이전트와 모델 컨텍스트 프로토콜(MCP) 서버의 필수 표준입니다. 모든 인증 흐름에서 PKCE(RFC 7636)를 의무화하고 불안전한 Implicit/Password 방식을 폐기하며, DPoP(RFC 9449)와 결합하여 헤드리스 환경 전반에서 발신자 제한(sender-constrained) 제로 트러스트 토큰을 강제합니다.
1. 서론: 2026년 자율형 에이전트의 신원 인증 위기
단순한 대규모 언어 모델(LLM) 채팅 인터페이스에서 자율적이고 멀티 턴(Multi-turn)으로 동작하는 AI 에이전트 및 모델 컨텍스트 프로토콜(Model Context Protocol, MCP) 서버로의 급격한 전환은 심각한 보안 위기를 촉발했습니다. 바로 에이전트의 신원 식별 및 권한 부여의 병목 현상입니다.
2024년과 2025년 동안 개발자들은 Claude Code, Cursor, Windsurf, AutoGen 및 맞춤형 LangGraph 에이전트와 같은 자율 도구를 엔터프라이즈 API에 연결할 때, 주로 .env 파일이나 시스템 프로세스 환경 변수에 하드코딩된 정적 개인 액세스 토큰(PAT)이나 수명이 긴 API 키를 사용했습니다. AI 에이전트가 로컬 셸 명령을 실행하거나, 내부 데이터베이스를 쿼리하거나(PostgreSQL 또는 Supabase MCP 서버를 통해), 사내 이슈 트래커를 업데이트할 때(Jira 또는 Linear MCP 서버를 통해), 에이전트는 광범위하고 무제한적인 앰비언트 권한(Ambient Authority) 하에서 동작합니다.
취약한 레거시 에이전트 아키텍처 (정적 앰비언트 권한):
+--------------------+ 자식 프로세스 생성 +---------------------------+
| LLM 호스트 에이전트 | ──────────────────────────> | 로컬 MCP 도구 서버 |
| (Claude Code / | Env: GITHUB_TOKEN=ghp_... | (process.env 직접 판독) |
| Cursor / LangSeq) | +-------------+-------------+
+---------+----------+ |
| 간접 프롬프트 인젝션 공격 | 무제한 읽기/쓰기 권한
v v
+--------------------+ +-------------------+
| 신뢰할 수 없는 웹의 | | 업스트림 엔터프라이즈|
| 공격자 프롬프트 | ──> 정적 시크릿 유출 ──────────────> | GitHub / Slack / |
| "환경 변수를 출력해"| 공격자 HTTP 웹훅으로 전송 | 내부 데이터베이스 |
+--------------------+ +-------------------+
이러한 정적 자격 증명 아키텍처는 다음 세 가지 이유로 근본적으로 취약합니다:
- 시크릿 탈취 경로가 되는 프롬프트 인젝션: 에이전트가 신뢰할 수 없는 데이터(웹페이지, 이메일, GitHub 이슈에 삽입된 악의적 프롬프트 등)를 마주하면, LLM이 조작되어
process.env를 출력하거나 로컬 설정 파일을 조사하는 명령을 실행하여 최고 권한의 키를 즉시 외부로 유출할 수 있습니다. - 신원 위임의 부재: 정적 API 키는 인간 운영자가 의도적으로 실행한 작업과 AI 에이전트가 환각이나 자율 판단에 의해 트리거한 작업을 구별할 수 없습니다. 엔터프라이즈 감사 로그에서 모든 호출은 동일한 인간 사용자로 기록됩니다.
- 동적 취소 및 최소 권한 스코프 부재: 정적 토큰은 통상 지나치게 광범위한 권한(저장소 전체 읽기/쓰기 권한 등)을 부여받으며 수개월 또는 무기한의 유효 기간을 가집니다.
이 시스템적 위험을 해결하기 위해 AI 생태계는 위임 권한 부여 프레임워크로 수렴했습니다. 그러나 OAuth 1.0a, OAuth 2.0, 그리고 최신 통합 표준인 OAuth 2.1 간의 아키텍처 선택—여기에 PKCE (RFC 7636), DPoP (RFC 9449), Device Authorization Grant (RFC 8628)의 결합—을 결정하려면, 이러한 프로토콜이 헤드리스 및 자율 실행 제약 하에서 어떻게 동작하는지 정확히 이해해야 합니다.
2. OAuth 진화: 1.0a vs 2.0 vs 2.1 구조적 비교
현대 AI 에이전트 프레임워크가 왜 OAuth 2.1을 필수로 요구하는지 이해하기 위해, OAuth 사양의 3대 주요 버전에 걸친 아키텍처 진화, 트레이드오프, 핵심 취약점을 분석합니다.
OAUTH 사양 진화 프로세스 (2007 - 2026):
+---------------------------------------------------------------------------------------------+
| OAuth 1.0a (RFC 5849, 2010) |
| - 모든 개별 HTTP 요청마다 대칭/비대칭 암호화 서명 생성 (HMAC-SHA1, RSA-SHA1) |
| - 토큰 리프레시 흐름 부재, 상태 의존적 서명 계산, 전송 계층 독립적 보안 |
| - AI 에이전트 평가: 사용 불가. 암호화 오버헤드가 스트리밍 및 동적 LLM 도구 프록시를 붕괴시킴. |
+---------------------------------------------------------------------------------------------+
│
▼
+---------------------------------------------------------------------------------------------+
| OAuth 2.0 (RFC 6749 & RFC 6750, 2012) |
| - 암호화 무결성을 전송 계층 보안(TLS 1.2/1.3)으로 위임 |
| - Bearer 토큰, 스코프, 리프레시 토큰, 특화된 그랜트 유형 도입 |
| - Implicit Flow 및 리소스 소유자 비밀번호 자격 증명(ROPC) 포함 |
| - AI 에이전트 평가: 위험. 프롬프트 인젝션이나 SSRF를 통해 Bearer 토큰이 쉽게 탈취 및 재전송됨.|
+---------------------------------------------------------------------------------------------+
│
▼
+---------------------------------------------------------------------------------------------+
| OAuth 2.1 (IETF 통합 표준, 2025/2026) |
| - 취약한 구형 그랜트 완전 제거 (Implicit 및 Password 그랜트 영구 삭제) |
| - 모든 인가 코드 흐름에서 PKCE (RFC 7636) 의무화 (퍼블릭 및 기밀 클라이언트 모두 적용) |
| - 리디렉션 URI 바이트 단위 완전 일치 검증, URI 쿼리 매개변수 내 토큰 전달 엄격 금지 |
| - 리프레시 토큰 로테이션(RTR) 또는 발신자 제한 토큰(DPoP/mTLS) 강제 요구 |
| - AI 에이전트 평가: MCP 서버 및 자율 에이전트 신원 위임을 위한 최고 수준의 표준. |
+---------------------------------------------------------------------------------------------+
OAuth 1.0a (RFC 5849): 암호화 엄격성과 상태 의존성
OAuth 1.0a는 HTTPS/TLS가 값비싸고 널리 보급되지 않았던 시대에 탄생했습니다. 일반 텍스트 HTTP 도청을 방지하기 위해 OAuth 1.0a는 클라이언트와 서버가 개별 HTTP 요청마다 암호화 서명(HMAC-SHA1 또는 RSA-SHA1)을 직접 계산하도록 요구했습니다.
서명 계산을 위해서는 HTTP 메서드, 정규화된 URL, 그리고 사전순으로 정렬된 모든 쿼리 매개변수, 요청 헤더, 클라이언트 Nonce, Unix 타임스탬프를 조합해야 했습니다:
$$\text{BaseString} = \text{HTTP\_METHOD} \mathbin{\Vert} \text{"\&"} \mathbin{\Vert} \text{Encode}(\text{URL}) \mathbin{\Vert} \text{"\&"} \mathbin{\Vert} \text{Encode}(\text{SortedParams})$$
$$\text{Signature} = \text{HMAC-SHA1}(\text{ClientSecret} \mathbin{\Vert} \text{"\&"} \mathbin{\Vert} \text{TokenSecret}, \text{BaseString})$$
OAuth 1.0a가 AI 에이전트 환경에서 실패하는 이유:
- 스트리밍 및 청크 전송 비호환: Server-Sent Events나 WebSocket 기반의 MCP와 같은 현대 에이전트 프로토콜은 점진적인 JSON-RPC 페이로드를 스트리밍합니다. 비결정론적 스트리밍 청크나 동적 프록시 헤더 재작성에 대해 서명을 재계산하면 서명 검증이 지속적으로 실패합니다.
- 동적 도구 오케스트레이션 충돌: AI 에이전트는 LLM 도구 매개변수를 바탕으로 HTTP 요청을 동적으로 구성합니다. 약간의 매개변수 순서 변경, URL 인코딩 차이(
%20대+), 또는 프록시가 삽입한 헤더로 인해 서명이 무효화되어 자율 루프에서 401 Unauthorized 오류가 끊임없이 발생합니다. - 네이티브 리프레시 분리 부재: OAuth 1.0a 토큰에는 자동 로테이션 메커니즘을 갖춘 단기 토큰 수명 주기가 없어, 장기 자격 증명이 클라이언트 환경에 영구히 상주해야 했습니다.
OAuth 2.0 (RFC 6749): Bearer 위험과 맞바꾼 간결성
OAuth 2.0은 암호화 무결성을 전송 계층(HTTPS 의무화)으로 위임하고 Bearer 토큰(RFC 6750)을 도입하여 복잡성을 해소했습니다. 현금과 마찬가지로 토큰을 소지한 자는 누구나 리소스에 접근할 수 있습니다:
GET /v1/repositories HTTP/1.1
Host: api.github.com
Authorization: Bearer ya29.a0AfH6SMB...
OAuth 2.0은 4가지 기본 인가 그랜트 흐름을 정의했습니다:
- Authorization Code Grant: 안전한 백엔드 시크릿을 보유한 웹 애플리케이션을 위한 리디렉션 흐름.
- Implicit Grant: 백엔드 코드 교환 없이 브라우저 URL 해시(
#access_token=...)로 직접 토큰을 반환하는 흐름. - Resource Owner Password Credentials (ROPC): 사용자의 아이디와 비밀번호를 클라이언트 앱에 직접 입력하여 토큰을 발급받는 흐름.
- Client Credentials Grant: 사람의 개입이 없는 서버 데몬 간 기계 대 기계(M2M) 인증 흐름.
AI 시스템에서 OAuth 2.0의 치명적 결함:
- Bearer 재전송 취약점(Replay Attack): SSRF, 프롬프트 인젝션 또는 로그 유출을 통해 에이전트의 실행 컨텍스트가 침해당하면, 공격자는 Bearer 토큰을 탈취하여 만료될 때까지 전 세계 어디서나 악의적으로 재사용할 수 있습니다.
- Implicit Grant의 함정: 초기 단일 페이지 앱(SPA)과 데스크톱 에이전트 UI는 Implicit 흐름을 채택했습니다. 토큰은 브라우저 히스토리,
Referer헤더, 로컬 리디렉션 로그를 통해 수없이 유출되었습니다. - Password Grant 안티패턴: 터미널 CLI 에이전트 개발자들은 콘솔 프롬프트에서 사용자 비밀번호 입력을 유도하여 제3자에게 자격 증명을 공유하지 않는다는 OAuth의 기본 약속을 파괴했습니다.
OAuth 2.1: 자율형 에이전트를 위한 현대적 강화 표준
OAuth 2.1은 지난 세월 동안 누적된 기술 부채와 보안 취약점을 걷어낸 IETF 통합 사양입니다. 자율형 AI 시스템과 Model Context Protocol 연동을 위해 OAuth 2.1은 엄격한 아키텍처 규칙을 규정합니다:
- 불안전한 그랜트의 완전 제거: Implicit Grant와 Resource Owner Password Credentials Grant가 완전히 폐기 및 삭제되었습니다.
- 모든 인가 코드 흐름에서 PKCE 의무화: Proof Key for Code Exchange (RFC 7636)는 모바일 전용이 아니며, 퍼블릭 클라이언트(CLI 에이전트, IDE 확장) 및 기밀 클라이언트(백엔드 에이전트 스웜) 모두에 예외 없이 강제됩니다.
- 리디렉션 URI의 바이트 단위 완전 일치: 오픈 리디렉터 공격 및 와일드카드를 통한 서브도메인 탈취를 막기 위해 인가 서버는 리디렉션 URI 문자열을 완벽하게 검증해야 합니다.
- URI 쿼리 매개변수 내 토큰 전달 엄격 금지: HTTP 액세스 로그, 프록시 캐시, 브라우저 텔레메트리로 토큰이 새어나가는 것을 원천 봉쇄합니다.
- 리프레시 토큰 보호 의무화: 인가 서버는 리프레시 토큰 로테이션(RTR)(갱신 요청마다 구 토큰 무효화 및 새 쌍 발급) 또는 발신자 제한 토큰(DPoP 또는 mTLS)을 반드시 구현해야 합니다.
아키텍처 종합 비교표: OAuth 1.0a vs OAuth 2.0 vs OAuth 2.1
| 아키텍처 차원 | OAuth 1.0a (RFC 5849) | OAuth 2.0 (RFC 6749 / 6750) | OAuth 2.1 (IETF 표준 2026) |
|---|---|---|---|
| 암호화 모델 | 애플리케이션 계층 요청 서명 (HMAC-SHA1 / RSA) | 전송 계층 보안 (TLS) + 일반 Bearer | TLS + 필수 PKCE + 발신자 제한 (DPoP/mTLS) |
| Bearer 재전송 공격 위험 | 없음 (요청마다 고유 Nonce 서명) | 매우 높음 (소지가 곧 권한) | 근본적 방어 (DPoP 개인키 증명 바인딩) |
| PKCE 요구 여부 | 미지원 | 선택 사항 (RFC 7636, 주로 모바일) | 모든 인가 코드 교환에서 필수 |
Implicit Grant (response_type=token) |
미지원 | 허용 (브라우저 SPA용 설계) | 완전 삭제 및 영구 금지 |
| Password Grant (ROPC) | 미지원 | 허용 (레거시 직접 자격 증명 교환) | 완전 삭제 및 영구 금지 |
| 리디렉션 URI 검증 | 접두사 일치 허용 | 와일드카드 및 경로 일치 빈번 허용 | 엄격한 바이트 단위 완전 일치 필수 |
| URI 매개변수 내 토큰 | 지원 | 허용 (?access_token=...) |
완전 금지 (헤더 또는 바디만 허용) |
| 리프레시 토큰 수명 주기 | 네이티브 메커니즘 없음 | 단일 토큰을 취소 시까지 영구 재사용 | 의무적 로테이션 (RTR) 또는 암호화 바인딩 |
| 로컬 CLI 에이전트 적합성 | 극히 불량 (셸 자동화 서명 취약) | 취약 (로컬 루프백 리디렉션 가로채기) | 최적 (PKCE + 루프백 임시 포트) |
| 원격 MCP 서버 적합성 | 스트리밍 JSON-RPC와 호환 불가 | 사용 가능하나 시크릿 유출 위험 높음 | 기본 표준 (엄격한 최소 권한 스코프) |
3. 모델 컨텍스트 프로토콜 (MCP) 인증 토폴로지: 에이전트-서버 통신 보안
Anthropic이 오픈소스로 공개하고 Claude Code, Cursor, Windsurf 및 엔터프라이즈 에이전트 런타임에 채택된 모델 컨텍스트 프로토콜(MCP)은 JSON-RPC 2.0을 기반으로 비대칭 클라이언트-서버 구조를 확립합니다.
OAuth 2.1이 작용하는 위치를 파악하기 위해 MCP 배포 환경 내 두 가지 통신 경계를 식별해야 합니다:
- 경계 A (호스트에서 MCP 서버로): LLM 클라이언트 애플리케이션(Claude Code, Cursor 등)과 MCP 서버 프로세스 간의 연결.
- 경계 B (MCP 서버에서 엔터프라이즈 업스트림으로): MCP 서버와 외부 SaaS API(GitHub, Linear, Jira, Slack 등) 간의 연결.
모델 컨텍스트 프로토콜 (MCP) 인증 토폴로지:
+-------------------------------------------------------------------------------------------------------+
| MCP 호스트 런타임 (예: Claude Code / Cursor / 자율 에이전트 하네스) |
| |
| +---------------------+ 프롬프트 컨텍스트 +--------------------------------------------+ |
| | 사용자 프롬프트 / | <───────────────────────────> | LLM 추론 엔진 (Claude 3.7 / GPT-4o) | |
| | 오케스트레이터 루프 | +--------------------------------------------+ |
| +----------+----------+ |
| | 도구 호출 디스패치 (`tools/call`) |
| v |
| +--------------------------------------------------------------------------------------------------+ |
| | MCP 클라이언트 엔진 | |
| | - 인가 서버와의 OAuth 2.1 PKCE 핸드셰이크 관리 | |
| | - 내보내기 불가능한 메모리에 임시 DPoP 비대칭 개인키 보관 | |
| | - 요청마다 DPoP Proof JWT 생성; JSON-RPC 헤더에 Access Token 주입 | |
| +-----------------------------------+--------------------------------------------------------------+ |
+--------------------------------------|----------------------------------------------------------------+
|
| 전송 계층: Stdio (로컬 프로세스) 또는 SSE/HTTP (원격 서버)
v
+-------------------------------------------------------------------------------------------------------+
| MCP 서버 런타임 (예: GitHub MCP / 엔터프라이즈 DB MCP) |
| |
| +--------------------------------------------------------------------------------------------------+ |
| | 인증 및 토큰 검증 인터셉터 | |
| | 1. 인가 서버의 JWKS를 통해 OAuth 2.1 토큰 서명 검증 | |
| | 2. DPoP Proof 검증: HTTP 메서드, URI, Nonce 및 임시 공개키 일치 여부 확인 | |
| | 3. 스코프 평가: 최소 권한 강제 (예: `issues:read` 허용, `admin:all` 거부) | |
| +-----------------------------------+--------------------------------------------------------------+ |
| | |
| v |
| +--------------------------------------------------------------------------------------------------+ |
| | MCP 도구 실행 엔진 (`tools/call` 구현체) | |
| | - 입력값 정제, 경로 순회 차단, 샌드박스 환경 내 API 호출 실행 | |
| +-----------------------------------+--------------------------------------------------------------+ |
+--------------------------------------|----------------------------------------------------------------+
| 위임된 스코프 제한 토큰을 사용한 업스트림 API 호출
v
+----------------------------------+
| 엔터프라이즈 SaaS / 데이터베이스 |
| (GitHub / Jira / PostgreSQL / S3)|
+----------------------------------+
Stdio vs 원격 SSE/HTTP 전송 방식 비교
- 로컬 Stdio 전송 (
transport: "stdio"):
- MCP 서버는 호스트가 생성한 로컬 자식 프로세스로 실행되며 표준 입력(stdin)과 표준 출력(stdout)을 통해 통신합니다.
- 보안 안티패턴: 관행적으로 개발자들은 설정 파일 환경 변수에 정적 자격 증명을 주입해 왔습니다:
- 취약점: 에이전트가 실행하는 셸 명령이나 동일 컨테이너 내에서 생성된 모든 하위 프로세스는
/proc/[pid]/environ을 읽거나env를 호출하여 조직의 전체 GitHub 액세스 권한을 즉시 탈취할 수 있습니다. - OAuth 2.1 솔루션: 호스트가 OAuth 2.1 PKCE 토큰 볼트를 관리합니다. MCP 서버는 시작 시 정적 시크릿 없이 초기화되며, 초기화 핸드셰이크 과정에서 단기 위임 토큰을 전달받거나 호스트가 인증된 역방향 프록시 역할을 수행합니다.
- 원격 SSE/HTTP 전송 (
transport: "sse"):
- MCP 서버는 HTTP 포트에서 수신 대기하는 분산 웹 서비스로 동작하며, Server-Sent Events를 활용해 클라이언트로 알림을 전송합니다.
- 이 아키텍처에서는 OAuth 2.1이 필수입니다. MCP 클라이언트는 표준 HTTP
Authorization헤더를 통해 원격 서버에 인증해야 하며, 서버는 JWKS를 통한 서명 검증과 DPoP 증명을 검증합니다.
4. PKCE (RFC 7636) 심층 분석: 에이전트 로컬 콜백 보호
코드 교환용 증명 키(Proof Key for Code Exchange, PKCE)는 모바일 디바이스에서 인가 코드가 가로채기 당하는 것을 방지하기 위해 RFC 7636에 정의되었습니다. OAuth 2.1에서는 모든 인가 코드 교환에 PKCE 적용이 의무화되었습니다.
CLI 에이전트와 IDE가 퍼블릭 클라이언트(Public Client)인 이유
Claude Code, Cursor, Roo Code와 같은 AI 도구는 OAuth 분류 체계상 퍼블릭 클라이언트에 해당합니다. 소스 코드나 바이너리 배포물이 사용자의 로컬 머신에서 직접 실행되므로 정적 client_secret을 안전하게 은닉할 수 없습니다. 오픈소스 CLI 도구에 시크릿을 포함시키면 바이너리 역공학을 통해 누구나 쉽게 추출할 수 있습니다.
퍼블릭 클라이언트가 인가를 요청하면 인가 서버는 로컬 리디렉션 URI(통상 http://127.0.0.1:18492/callback과 같은 임시 로컬 루프백 HTTP 서버)를 통해 인가 코드(Authorization Code)를 반환합니다.
인가 코드 가로채기 공격 시나리오 (PKCE 미적용 시):
1. 정상적인 CLI 에이전트가 인가 서버에 인가 코드를 요청함.
2. 개발자 머신의 악성 백그라운드 프로세스가 로컬 포트를 가로채거나 루프백 트래픽을 도청함.
3. 인가 서버가 브라우저를 http://127.0.0.1:18492/callback?code=AUTH_CODE_123 으로 리디렉션함.
4. 악성 프로세스가 AUTH_CODE_123 을 가로챔.
5. 악성 프로세스가 AUTH_CODE_123 을 /oauth/token 엔드포인트로 전송함.
퍼블릭 클라이언트이므로 client_secret이 필요 없어 인가 서버가 공격자에게 Access Token을 발급함!
PKCE의 수학적 방어 메커니즘
PKCE는 각 개별 인가 요청마다 동적으로 위조 불가능한 일회성 암호화 시크릿을 생성하여 이 취약점을 원천적으로 차단합니다.
PKCE 프로토콜 통신 시퀀스:
+-------------+ +-----------------------+ +--------------------+
| Agent CLI | | 사용자 브라우저 | | 인가 서버 |
| (클라이언트) | +-----------+-----------+ +---------+----------+
+------+------+ | |
| 1. code_verifier 생성 (고엔트로피) | |
| code_challenge = S256(...) 계산 | |
| | |
| 2. 루프백 HTTP 리스너 실행 | |
| challenge와 함께 브라우저 오픈 ────>| 3. GET /authorize?response_type=code |
| | &client_id=agent_cli |
| | &code_challenge=E9Melhoa2Owv... |
| | &code_challenge_method=S256 ─────────>|
| | | 4. 사용자 동의.
| | 5. 302 로컬 루프백으로 리디렉션 | challenge 보관
| |<─────────────────────────────────────────|
|<───────────────────────────────────────| http://127.0.0.1:18492/callback?code=AC_88921
| 6. 인가 코드가 포함된 콜백 수신 |
| |
| 7. POST /oauth/token |
| code=AC_88921 & code_verifier=dBjftJeZ4CVP-mB92K... ─────────────────────────>|
| | 8. 계산 검증:
| | SHA256(verifier)
| | == 저장된 challenge?
| 9. Access Token + Refresh Token (RTR) 반환 <──────────────────────────────────────| 일치: 토큰 발급
+------+------+
이 암호화 핸드셰이크는 다음과 같이 진행됩니다:
- Code Verifier 생성: AI 에이전트는 미예약 URL 문자(
[A-Z],[a-z],[0-9],-,.,_,~)로 구성된 43~128자 길이의 암호학적 고엔트로피 무작위 문자열 $V$를 생성합니다: - Code Challenge 계산: 클라이언트는 $V$의 SHA-256 해시를 계산하고 패딩 없는 Base64URL로 인코딩합니다:
- 인가 요청: 클라이언트는 $C$와 변환 방식
code_challenge_method=S256을/authorize엔드포인트로 전송합니다. 서버는 발급된 인가 코드와 함께 $C$를 안전하게 저장합니다. - 토큰 교환 및 검증: 클라이언트가
/token에서 인가 코드를 교환할 때 평문code_verifier=V를 전송합니다. 인가 서버는 $\text{Base64URL-Encode}(\text{SHA-256}(V))$를 계산하여 저장된 $C$와 완전 일치하는지 확인합니다.
로컬 머신의 악성 프로세스가 인가 코드를 가로채더라도, 에이전트 프로세스 메모리 외부에 유출된 적 없는 원본 code_verifier를 갖고 있지 않으므로 토큰으로 교환할 수 없습니다.
프로덕션 TypeScript 구현: 견고한 PKCE 엔진
로컬 MCP 클라이언트 런타임을 위해 설계된 엔터프라이즈급 PKCE 생성 및 검증 모듈입니다:
// pkce.ts - Enterprise OAuth 2.1 PKCE Engine for AI Agent Clients
import { randomBytes, createHash } from "node:crypto";
export interface PKCEChallenge {
codeVerifier: string;
codeChallenge: string;
codeChallengeMethod: "S256";
}
export class PKCEEngine {
/**
* Generates a cryptographically secure code_verifier (RFC 7636 Section 4.1)
* Length defaults to 64 bytes of entropy (yielding ~86 base64url characters).
*/
public static generateVerifier(length: number = 64): string {
if (length < 32 || length > 96) {
throw new RangeError("Verifier byte length must be between 32 and 96.");
}
const buffer = randomBytes(length);
return this.base64UrlEncode(buffer);
}
/**
* Computes the S256 code_challenge from the code_verifier (RFC 7636 Section 4.2)
*/
public static computeChallenge(verifier: string): string {
const hash = createHash("sha256").update(verifier, "ascii").digest();
return this.base64UrlEncode(hash);
}
/**
* Generates the complete PKCE pair ready for OAuth 2.1 authorization
*/
public static createPair(): PKCEChallenge {
const codeVerifier = this.generateVerifier(64);
const codeChallenge = this.computeChallenge(codeVerifier);
return {
codeVerifier,
codeChallenge,
codeChallengeMethod: "S256",
};
}
/**
* Server-side verification: Validates an incoming code_verifier against stored challenge
*/
public static verify(verifier: string, storedChallenge: string): boolean {
const computed = this.computeChallenge(verifier);
// Timing-safe buffer comparison to prevent side-channel timing attacks
const bufA = Buffer.from(computed);
const bufB = Buffer.from(storedChallenge);
if (bufA.length !== bufB.length) return false;
let result = 0;
for (let i = 0; i < bufA.length; i++) {
result |= bufA[i] ^ bufB[i];
}
return result === 0;
}
private static base64UrlEncode(buffer: Buffer): string {
return buffer
.toString("base64")
.replace(/\\+/g, "-")
.replace(/\\//g, "_")
.replace(/=+$/, "");
}
}
5. 헤드리스 서버 및 CLI 인가: 디바이스 플로우 (RFC 8628)
PKCE는 브라우저를 띄우고 로컬 루프백 포트를 바인딩할 수 있는 인터랙티브 데스크톱 환경의 인증을 해결하지만, 최신 AI 에이전트는 브라우저가 전혀 없는 헤드리스 환경에서 작동하는 경우가 대다수입니다:
- 원격 클라우드 클러스터의 Docker 컨테이너(AWS ECS, Kubernetes, Fly.io).
- 일회성 CI/CD 러너(GitHub Actions, GitLab CI).
- 원격 VM 및 터미널 SSH 세션.
헤드리스 환경에서는 리디렉션을 완료하기 위한 브라우저 창을 띄울 수 없습니다. 터미널에 비밀번호를 직접 입력하게 하는 방식은 OAuth 2.1 규격에 정면으로 위배됩니다. 이를 해결하는 표준 아키텍처는 OAuth 2.0 Device Authorization Grant (RFC 8628)입니다.
헤드리스 에이전트 환경에서의 Device Authorization Grant (RFC 8628):
+-------------------+ +-----------------------+
| 헤드리스 에이전트 | | 인가 서버 |
| (Docker / 클라우드)| +-----------+-----------+
+---------+---------+ |
| 1. POST /oauth/device/code |
| (client_id, scope) ──────────────────────────────────────────────>|
| | 2. 생성:
| 3. 디바이스 자격 증명 반환: | device_code (비밀)
| - user_code: "WDJB-HGNP" | user_code (공개)
| - verification_uri: "https://auth.corp.com/activate" | interval: 5초
| - interval: 5 <───────────────────────────────────────────────────|
| |
| 4. 콘솔에 사용자 안내 메시지 출력: |
| "https://auth.corp.com/activate 접속 후 코드 입력: WDJB-HGNP" |
| |
| 5. 폴링 루프 진입: |
| POST /oauth/token (grant_type=device_code, device_code=...) ─────>|
| <── 400 Bad Request: {"error": "authorization_pending"} ─────────|
| [5초 대기] |
| |
+---------+---------+ 사용자가 개인 노트북 또는 스마트폰으로 URL 접속 |
| 사용자 노트북 | ──> "WDJB-HGNP" 입력 후 사내 MFA 인증 완료 ───────────────>| 6. 관리자 승인 완료!
+-------------------+ |
| |
| 7. 다음 폴링 사이클: |
| POST /oauth/token ───────────────────────────────────────────────>|
| <── 200 OK: {access_token: "...", refresh_token: "..."} ──────────|
v
[헤드리스 에이전트가 비밀 노출 없이 안전하게 인증 완료]
기계 대 기계(M2M) 대안: RFC 7523 비공개 키 JWT
자율 AI 에이전트가 사람의 개입 없이 완전 무인으로 동작하는 경우(예: 야간 자동 코드 리팩터링 봇), 승인할 사람이 없으므로 Device Authorization Grant도 적합하지 않습니다.
이 시나리오에서는 RFC 7523(클라이언트 인증을 위한 JWT 프로파일) 기반의 Client Credentials Grant가 제로 트러스트 아키텍처의 표준입니다:
- 네트워크 상에 정적
client_secret을 전송하는 대신, 에이전트는 HSM 또는 Kubernetes Secret Vault에 안전하게 탑재된 비대칭 개인키(RSA 또는 ECDSA)를 보유합니다. /oauth/token에 인증할 때 에이전트는 고유 UUIDjti, 60초의 매우 짧은 유효 기간, 대상 대상자aud가 포함된 임시 JWT를 직접 서명하여 전송합니다.- 인가 서버는 사전 등록된 에이전트의 공개키로 서명을 검증하므로, 어떠한 공유 시크릿도 네트워크를 통과하지 않습니다.
6. 토큰 수명 주기 및 자율 갱신 워크플로
자율 AI 에이전트는 대규모 코드베이스 인덱싱, 대규모 벤치마크 실행, 장애 모니터링 등 수 시간에 걸친 장기 작업을 수행합니다. OAuth 2.1의 Access Token은 노출 위험을 최소화하기 위해 의도적으로 수명이 짧게(통상 5~15분) 발행되므로, 에이전트는 활성화된 LLM 도구 호출을 중단하지 않고 토큰 수명 주기를 자율적으로 관리해야 합니다.
리프레시 토큰 로테이션(RTR) 및 침해 탐지
OAuth 2.1에서 리프레시 토큰은 탈취로부터 철저히 보호되어야 합니다. 그 핵심 기술이 리프레시 토큰 로테이션(Refresh Token Rotation, RTR)입니다:
- 에이전트가
/oauth/token에refresh_token을 제출할 때마다 인가 서버는 해당 리프레시 토큰을 즉시 무효화합니다. - 서버는 완전히 새로운
access_token과 새로운refresh_token쌍을 발행합니다. - 만약 공격자가 이미 사용된 이전 리프레시 토큰을 탈취하여 재사용을 시도하면, 인가 서버는 즉각적인 침해 사고로 인식합니다:
$$\text{Incoming Token State} == \text{"REVOKED"} \implies \text{Revoke All Tokens in Family Tree}$$
인가 서버는 즉시 해당 권한 패밀리 트리 전체를 파기하여, 모든 에이전트 인스턴스에 발급된 활성 Access Token과 Refresh Token을 즉각 무효화합니다.
리프레시 토큰 로테이션 (RTR) 및 침해 자동 복구:
토큰 세대 체인:
[Refresh Token A] ──(사용 완료)──> [Refresh Token B] ──(사용 완료)──> [Refresh Token C] (현재 활성)
│
│ 공격자가 탈취한 구형 [Refresh Token A] 재전송 시도
v
[인가 서버가 폐기된 토큰 A의 재사용 시도를 즉시 감지!]
│
▼
[심각 보안 경보]: Token B, Token C 및 연관된 모든 Access Token 즉시 일괄 파기.
에이전트 세션이 안전하게 종료되어 권한 상승 공격 차단.
Python 프로덕션 구현: 스레드 안전 비동기 토큰 매니저
다중 에이전트 시스템(예: 1개의 오케스트레이터가 동일한 MCP 서버를 쿼리하는 10개의 병렬 서브 에이전트를 생성하는 경우)에서는 여러 동시 도구 호출이 동시에 토큰 만료를 감지할 수 있습니다. 10개의 서브 에이전트가 단일 사용 리프레시 토큰으로 동시에 갱신을 시도하면 9개는 실패하게 되며, 인가 서버는 이러한 동시성 경쟁을 토큰 재사용 공격으로 오인하여 전체 세션을 종료할 수 있습니다!
다음은 비동기 뮤텍스 락, 선제적 갱신, 동시성 조정을 구현한 실전 Python 모듈입니다:
# token_manager.py - Enterprise Async Token Lifecycle Manager for AI Agents
import asyncio
import time
import httpx
from typing import Optional, Dict, Any
class AgentTokenManager:
def __init__(
self,
token_endpoint: str,
client_id: str,
initial_refresh_token: str,
proactive_refresh_seconds: int = 60,
):
self.token_endpoint = token_endpoint
self.client_id = client_id
self.refresh_token = initial_refresh_token
self.access_token: Optional[str] = None
self.expires_at: float = 0.0
self.proactive_refresh_seconds = proactive_refresh_seconds
self._lock = asyncio.Lock()
async def get_valid_access_token(self) -> str:
now = time.time()
if self.access_token and (self.expires_at - now) > self.proactive_refresh_seconds:
return self.access_token
async with self._lock:
now = time.time()
if self.access_token and (self.expires_at - now) > self.proactive_refresh_seconds:
return self.access_token
await self._refresh_token_exchange()
if not self.access_token:
raise RuntimeError("Failed to acquire valid access token from authorization server.")
return self.access_token
async def _refresh_token_exchange(self) -> None:
payload = {
"grant_type": "refresh_token",
"refresh_token": self.refresh_token,
"client_id": self.client_id,
}
async with httpx.AsyncClient(timeout=10.0) as client:
try:
response = await client.post(
self.token_endpoint,
data=payload,
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
except httpx.RequestError as exc:
raise ConnectionError(f"Network transport error during token refresh: {exc}")
if response.status_code == 200:
data: Dict[str, Any] = response.json()
self.access_token = data["access_token"]
expires_in = int(data.get("expires_in", 3600))
self.expires_at = time.time() + expires_in
# Update to the newly rotated refresh token if provided
if "refresh_token" in data:
self.refresh_token = data["refresh_token"]
elif response.status_code in (400, 401):
err_data = response.json()
# If error is 'invalid_grant', the token was likely already rotated or revoked
raise PermissionError(f"Token refresh rejected (possible token theft or expiry): {err_data}")
else:
response.raise_for_status()
7. 제로 트러스트 자격 증명 격리: DPoP (RFC 9449) 및 하드웨어 인클레이브
OAuth 2.1과 PKCE를 엄격히 적용하더라도 기존의 Bearer 토큰에는 치명적인 아키텍처 약점이 존재합니다. 바로 토큰이 탈취되면 누구나 이를 사용할 수 있다는 점입니다.
AI 통합 환경에서 자율 에이전트는 신뢰할 수 없는 외부 입력에 대해 복잡한 추론을 일상적으로 수행합니다. 공격자가 간접 프롬프트 인젝션을 통해 인증 헤더를 붙여 공격자 제어 서버로 HTTP 요청을 보내도록 에이전트를 유도(SSRF)하면 Bearer 토큰은 즉각 침해됩니다.
진정한 제로 트러스트 보안을 달성하기 위해 OAuth 2.1은 DPoP: 애플리케이션 계층 소지 증명(RFC 9449 Demonstrating Proof-of-Possession)을 통합합니다.
DPOP (RFC 9449) 애플리케이션 계층 발신자 제한 아키텍처:
+-------------------------------------------------------------------------------------------------+
| 에이전트 로컬 환경 (클라이언트) |
| - 임시 키 쌍 생성: 공개키 (JWK) + 개인키 (RAM 또는 보안 인클레이브 외부에 절대 노출되지 않음) |
+-------------------------------------------------------------------------------------------------+
│
│ 1. DPoP Proof 헤더 첨부:
│ DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Ar...
│ 페이로드: {
│ "htm": "GET",
│ "htu": "https://api.enterprise.com/mcp/tools",
│ "iat": 1772630400,
│ "jti": "random_nonce_9921",
│ "jwk": { ...공개키 구조체... }
│ }
│
│ 2. 바인딩된 DPoP 토큰 첨부:
│ Authorization: DPoP dpop_access_token_88921
v
+-------------------------------------------------------------------------------------------------+
| 엔터프라이즈 MCP 게이트웨이 (리소스 서버) |
| 1. Access Token이 JWK의 공개키 지문과 암호학적으로 일치하는지 검증. |
| 2. DPoP Proof 서명이 공개키와 완전히 일치하는지 검증. |
| 3. "htm"이 "GET"이고 "htu"가 정확한 대상 URL과 일치하는지 검증. |
| 4. "iat"가 허용 오차 범위 내(< 60초)이고 "jti"가 재사용되지 않았는지 검증. |
+-------------------------------------------------------------------------------------------------+
│
┌────────────────────────────────────────┴────────────────────────────────────────┐
▼ ▼
[유효한 DPoP PROOF 및 일치하는 개인키] [탈취된 토큰 재전송 공격]
요청이 도구 실행 엔진으로 정상 진행됨 공격자가 토큰은 쥐고 있으나, 에이전트
로컬 메모리의 비대칭 개인키가 없음.
결과: 401 Unauthorized로 원천 차단!
DPoP의 실제 작동 원리
- 임시 키 쌍 생성: 에이전트 세션이 초기화될 때 휘발성 메모리 또는 보안 인클레이브 내에서 비대칭 암호화 키 쌍(일반적으로 P-256 타원곡선 ECDSA 또는 Ed25519)을 생성합니다.
- 암호화 바인딩: 에이전트가 인가 서버에 토큰을 요청할 때 DPoP 증명을 동봉합니다. 발급된 Access Token 내부에는 해당 공개키의 SHA-256 지문(
jkt클레임)이 영구 결합됩니다. - 요청별 소지 증명 서명: 이후 MCP 서버로 향하는 모든 개별 API 호출마다 에이전트는 일회성 DPoP JWT 증명을 서명합니다:
htm: 정확한 HTTP 요청 메서드(예:POST).htu: 쿼리 및 프래그먼트가 제거된 정확한 대상 URI.iat: 생성 타임스탬프($\pm 60$ 초 이내).jti: 재전송 공격을 막기 위한 고유 UUID.nonce: 서버가 요구한 도전 과제 Nonce(활성화된 경우).
- 심층 방어 효과: 프롬프트 유출이나 프록시 로그를 통해 Access Token 문자열이 탈취되더라도, 일치하는 DPoP 증명 헤더를 서명할 로컬 개인키가 없으므로 공격자는 토큰을 전혀 사용할 수 없습니다.
8. 엔터프라이즈 보안 벤치마크, 위험 매트릭스 및 장애 모드
자율 AI 에이전트를 위한 인증 메커니즘을 배포하려면 연산 지연 시간과 위협 방어 성능 사이의 균형을 유지해야 합니다. 다음은 다양한 인증 아키텍처의 지연 시간, 암호화 연산 부하, 보안 보장을 실측 비교한 결과입니다.
실측 성능 벤치마크 (10,000회 반복 측정, Apple M4 Max 하드웨어)
| 인증 아키텍처 | 클라이언트 핸드셰이크 지연 (p50) | 클라이언트 핸드셰이크 지연 (p99) | 요청당 검증 오버헤드 | 재전송 공격 방어력 | 클라이언트 메모리 점유율 | 서버 CPU 부하 증가 |
|---|---|---|---|---|---|---|
| 정적 PAT / API 키 | 0.1 ms (핸드셰이크 없음) | 0.2 ms | 0.02 ms (문자열 단순 비교) | 전무 (완전 재전송 가능) | < 1 KB | 기준치 |
| OAuth 1.0a (HMAC-SHA1) | 14.2 ms | 38.5 ms | 1.84 ms (전체 서명 파싱) | 부분 방어 (Nonce 검증) | 12 KB | +18% |
| OAuth 2.0 Bearer | 45.1 ms | 112.0 ms | 0.15 ms (JWT 서명 캐싱) | 전무 (Bearer 재전송) | 18 KB | +4% |
| OAuth 2.1 (PKCE + RTR) | 48.6 ms | 118.4 ms | 0.16 ms (JWT 검증) | 양호 (RTR 체인 취소) | 24 KB | +5% |
| OAuth 2.1 + DPoP (P-256) | 54.2 ms | 132.8 ms | 1.22 ms (DPoP 서명 검증) | 최고 (재전송 완전 불가능) | 36 KB | +12% |
| mTLS (RFC 8705) | 62.8 ms | 154.1 ms | 0.45 ms (TLS 세션 캐싱) | 최고 (인증서 하드 바인딩) | 128 KB | +15% |
엔터프라이즈 에이전트 위협 위험 매트릭스
위협 심각도 및 프로토콜 방어 매트릭스:
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 공격 벡터 | 정적 API 키 | OAuth 1.0a | OAuth 2.0 Bearer | OAuth 2.1 + DPoP |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 1. 간접 프롬프트 인젝션 | 치명적 (10/10) | 높은 위험 (7/10) | 치명적 (10/10) | 낮은 위험 (2/10) |
| (환경 변수/로그 유출) | 영구 슈퍼키 유출 | 서명 생성이 복잡 | Bearer 탈취 즉시 | 도난 토큰 재사용 |
| | | | 악용 가능 | 원천 차단 |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 2. 로컬 포트 스니핑 | 해당 없음 | 낮은 위험 (3/10) | 높은 위험 (8/10) | 완벽 방어 (1/10) |
| (CLI 루프백 가로채기) | 리디렉션 없음 | Nonce 서명 검증 | 인가 코드 탈취 | PKCE가 원천 차단 |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 3. SSRF 도구 피벗 공격 | 치명적 (10/10) | 중간 위험 (5/10) | 치명적 (10/10) | 완벽 방어 (1/10) |
| (공격자 서버로 유도) | 인증 헤더 유출 | URI 불일치 실패 | 토큰 재전송 성공 | DPoP URI 불일치 |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 4. 하위 프로세스 환경 변수 탐색| 치명적 (10/10) | 중간 위험 (5/10) | 높은 위험 (8/10) | 낮은 위험 (2/10) |
| (/proc/environ 직접 읽기) | 정적 키 영구 노출 | 환경에 키 노출 | 환경에 토큰 노출 | 단기 수명/키 바인딩|
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 5. 토큰 갱신 경쟁 상태 | 해당 없음 | 해당 없음 | 낮은 위험 (2/10) | 주의 (비동기 뮤텍스|
| (대규모 에이전트 스웜) | 갱신 없음 | 갱신 없음 | 토큰 재사용 실패 | 매니저 도입 필수) |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
9. 단계별 실전 가이드: 강화된 OAuth 2.1 + PKCE MCP 서버 구축
이 아키텍처 표준을 실무에 적용하기 위해, TypeScript, Express 및 JSON-RPC 2.0을 사용하여 프로덕션 레디 원격 Model Context Protocol (MCP) 서버를 구축합니다. 이 서버는 OAuth 2.1 토큰 검증, PKCE 검증, AI 에이전트 도구 호출에 대한 정밀한 스코프 강제를 수행합니다.
아키텍처 개요
- 인증 미들웨어: 엔터프라이즈 IdP의 JWKS 엔드포인트를 통해 유입되는 Bearer 및 DPoP 토큰을 암호학적으로 검증합니다.
- 스코프 가드(Scope Guard): 세밀한 권한을 엄격히 강제합니다(코드 검색 도구에
code:read만 허용하고code:write나 관리자 권한은 철저히 차단). - 도구 격리 샌드박스: 검증된 신원 컨텍스트 내에서 도구 실행 로직을 격리 처리합니다.
완성된 TypeScript MCP 서버 구현 코드
// server.ts - Hardened Remote MCP Server with OAuth 2.1 Validation
import express, { Request, Response, NextFunction } from "express";
import { createRemoteJWKSet, jwtVerify } from "jose";
const app = express();
app.use(express.json());
// Configuration
const ISSUER = "https://auth.enterprise.com/";
const AUDIENCE = "https://mcp.enterprise.com/";
const JWKS_URI = new URL("https://auth.enterprise.com/.well-known/jwks.json");
const JWKS = createRemoteJWKSet(JWKS_URI);
interface AuthenticatedRequest extends Request {
tokenClaims?: any;
}
/**
* Enterprise OAuth 2.1 Token Validation Middleware
*/
async function requireOAuth21(
req: AuthenticatedRequest,
res: Response,
next: NextFunction
): Promise<void> {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
res.status(401).json({
jsonrpc: "2.0",
error: { code: -32001, message: "Missing or invalid OAuth 2.1 Authorization header." },
id: req.body?.id || null,
});
return;
}
const token = authHeader.split(" ")[1];
try {
// Cryptographically verify token signature, issuer, audience, and expiration
const { payload } = await jwtVerify(token, JWKS, {
issuer: ISSUER,
audience: AUDIENCE,
});
// Enforce OAuth 2.1 requirement: reject tokens without an expiration claim
if (!payload.exp || typeof payload.exp !== "number") {
res.status(401).json({
jsonrpc: "2.0",
error: { code: -32002, message: "Non-compliant token: missing expiration claim." },
id: req.body?.id || null,
});
return;
}
req.tokenClaims = payload;
next();
} catch (err: any) {
res.status(401).json({
jsonrpc: "2.0",
error: { code: -32003, message: `Token verification failed: ${err.message}` },
id: req.body?.id || null,
});
}
}
/**
* Fine-Grained Scope Enforcement Guard
*/
function requireScope(requiredScope: string) {
return (req: AuthenticatedRequest, res: Response, next: NextFunction): void => {
const scopes: string[] = (req.tokenClaims?.scope || "").split(" ");
if (!scopes.includes(requiredScope)) {
res.status(403).json({
jsonrpc: "2.0",
error: {
code: -32004,
message: `Insufficient permissions: missing required scope '${requiredScope}'`,
},
id: req.body?.id || null,
});
return;
}
next();
};
}
/**
* Standard MCP JSON-RPC 2.0 Handler Endpoint
*/
app.post(
"/mcp/v1",
requireOAuth21,
requireScope("mcp:tools:execute"),
async (req: AuthenticatedRequest, res: Response): Promise<void> => {
const { jsonrpc, method, params, id } = req.body;
if (jsonrpc !== "2.0") {
res.status(400).json({ jsonrpc: "2.0", error: { code: -32600, message: "Invalid JSON-RPC version." }, id });
return;
}
// Router for MCP Primitives
switch (method) {
case "tools/list":
res.json({
jsonrpc: "2.0",
result: {
tools: [
{
name: "query_database",
description: "Executes read-only SQL queries against the analytics warehouse.",
inputSchema: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
},
},
],
},
id,
});
break;
case "tools/call":
if (params?.name === "query_database") {
// Verify elevated data scope for this specific tool execution
const scopes: string[] = (req.tokenClaims?.scope || "").split(" ");
if (!scopes.includes("db:analytics:read")) {
res.json({
jsonrpc: "2.0",
error: { code: -32005, message: "Forbidden: tool requires 'db:analytics:read' scope." },
id,
});
return;
}
// Execute tool with verified, scoped identity
const userSub = req.tokenClaims.sub;
console.log(`Executing query on behalf of verified agent identity: ${userSub}`);
res.json({
jsonrpc: "2.0",
result: {
content: [
{
type: "text",
text: JSON.stringify({ status: "success", rows_returned: 42, latency_ms: 12 }),
},
],
},
id,
});
} else {
res.status(404).json({ jsonrpc: "2.0", error: { code: -32601, message: "Tool not found." }, id });
}
break;
default:
res.status(404).json({ jsonrpc: "2.0", error: { code: -32601, message: "Method not found." }, id });
}
}
);
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Hardened OAuth 2.1 MCP Server listening on port ${PORT}`);
});
10. 결론 및 전략적 권장 사항 (E-E-A-T)
자율 AI 모델을 엔터프라이즈 인프라에 연결할 때, AI 에이전트는 반드시 부분 신뢰 위임 행위자(Semi-Trusted Delegated Actor)로 취급되어야 합니다. AI 에이전트를 루트 권한을 가진 완전 신뢰 내부 마이크로서비스로 취급하거나, 모든 단계마다 인간의 클릭을 요구하는 완전 불신 외부인으로 취급하는 것은 중대한 아키텍처 설계 오류입니다.
OAuth 2.1은 이러한 간극을 메우는 데 필요한 완벽한 암호화 프레임워크를 제공하여, 개발자의 자율성과 기업의 제로 트러스트 보안 준수를 동시에 실현합니다.
AI 보안 아키텍처 핵심 체크리스트 5개 항목
- 정적 환경 변수 시크릿 전면 폐기: 기존 MCP 설정 파일 및 컨테이너 구동 인자를 전수 감사하고, 정적 PAT를 수명이 짧은 OAuth 2.1 액세스 토큰으로 교체하십시오.
- S256 방식 PKCE 의무 적용: 모든 CLI 도구(Claude Code, Cursor 플러그인 등)가 고엔트로피 SHA-256 방식의 RFC 7636을 구현하도록 강제하고 안전하지 않은 plain 방식을 전면 차단하십시오.
- 헤드리스 환경에 RFC 8628 디바이스 플로우 또는 비공개 키 JWT 도입: 터미널 비밀번호 입력 스크립트를 폐기하고 유인 세션에는 Device Grant를, 무인 백그라운드 워커에는 RFC 7523 비대칭 키 인증을 적용하십시오.
- 동시성 뮤텍스를 적용한 리프레시 토큰 로테이션 배포: 에이전트 클라이언트 런타임에 비동기 배타 락을 구현하여 병렬 갱신 충돌과 의도치 않은 토큰 무효화를 방지하십시오.
- DPoP (RFC 9449) 발신자 제한 토큰 전면 도입: 코드 실행, 금융 트랜잭션, 데이터베이스 쓰기 등 고위험 작업에 대해 DPoP 헤더를 의무화하여 간접 프롬프트 인젝션 및 토큰 탈취 공격을 네트워크 전송 경계에서 완벽히 무력화하십시오.