MCP 보안과 인증
OAuth 2.0 토큰을 사용해 MCP 서버에 인증을 추가하고, 삽입 공격을 방지하도록 입력 검증을 구현하며, 도구 권한에 최소 권한 원칙을 적용합니다.
MCP 보안과 인증은(는) CoddyKit의 무료 AI Engineering Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Engineering Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
MCP 보안이 중요한 이유
MCP 서버는 시스템으로 들어가는 관문입니다. 적절한 보안이 없으면 손상된 AI 클라이언트나 악성 프롬프트가 민감한 데이터를 읽거나 파괴적인 작업을 실행하거나 도구 호출 채널을 통해 정보를 유출할 수 있습니다. MCP 서버 보안은 심층 방어 방식이어야 합니다. 전송 계층에서 인증하고, 도구 수준에서 권한을 부여하며, 모든 호출에서 입력을 검증해야 합니다.
로컬 서버와 원격 서버의 보안 비교
Claude Desktop에서 사용하는 stdio 전송에는 본질적인 보안 기능이 있습니다. 서버가 로컬 프로세스로 실행되므로 서버를 시작한 사용자만 접근할 수 있습니다. HTTP/SSE를 사용하는 네트워크 공개 MCP 서버는 인증 우회, 삽입 공격, 무단 접근 등 웹 보안 위협에 전부 노출됩니다. 보안 요구 사항은 배포 방식에 따라 크게 달라집니다.
- 로컬 stdio: 로컬 사용자를 신뢰하고 입력 검증에 집중합니다
- 원격 HTTP/SSE: 전체 인증, TLS, 속도 제한, 입력 정제가 필요합니다
원격 서버의 API 키 인증
원격 MCP 서버에서 가장 간단한 인증 방식은 HTTP 헤더를 통한 API 키 검증입니다. 들어오는 모든 요청에서 Authorization: Bearer <token> 헤더를 확인하고 인증되지 않은 요청에는 HTTP 401을 반환하여 거부합니다. 유효한 API 키는 사용자 메타데이터와 함께 데이터베이스에 저장하여 개별 키를 폐기할 수 있도록 합니다.
# For HTTP/SSE MCP servers using FastAPI or similar:
from fastapi import FastAPI, HTTPException, Depends, Header
from typing import Optional
import secrets
app_http = FastAPI()
# In production: store in database with user_id, created_at, last_used
VALID_KEYS = {'sk-mcp-abc123': {'user': 'alice', 'scopes': ['read']},
'sk-mcp-def456': {'user': 'bob', 'scopes': ['read', 'write']}}
async def verify_api_key(authorization: Optional[str] = Header(None)) -> dict:
if not authorization or not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail='Missing API key')
key = authorization.removeprefix('Bearer ')
if key not in VALID_KEYS:
raise HTTPException(status_code=401, detail='Invalid API key')
return VALID_KEYS[key] # Returns user context
# Use in route handlers:
# @app_http.get('/sse')
# async def sse_endpoint(user=Depends(verify_api_key)):엔터프라이즈 MCP 서버의 OAuth 2.0
엔터프라이즈 환경에서는 OAuth 2.0을 사용하여 사용자가 기업용 자격 증명 공급자(Okta, Azure AD, Google Workspace)로 인증하도록 합니다. MCP 클라이언트는 OAuth 액세스 토큰을 받아 도구 호출 요청에 포함합니다. 서버는 python-jose 또는 authlib를 사용하여 자격 증명 공급자의 공개 키로 토큰 서명을 검증합니다.
from jose import jwt, JWTError
import httpx
AUTH_DOMAIN = 'your-tenant.auth0.com'
AUDIENCE = 'https://api.your-mcp-server.com'
async def get_jwks():
async with httpx.AsyncClient() as client:
resp = await client.get(f'https://{AUTH_DOMAIN}/.well-known/jwks.json')
return resp.json()
async def verify_oauth_token(token: str) -> dict:
jwks = await get_jwks()
try:
payload = jwt.decode(
token,
jwks,
algorithms=['RS256'],
audience=AUDIENCE,
issuer=f'https://{AUTH_DOMAIN}/'
)
return payload # Contains sub (user ID), scope, exp, etc.
except JWTError as e:
raise ValueError(f'Invalid token: {e}')범위 기반 권한 부여
모든 MCP 도구를 모든 사용자가 사용할 수 있어야 하는 것은 아닙니다. OAuth 범위 또는 JWT 토큰의 역할 클레임을 사용하여 인증된 사용자가 호출할 수 있는 도구를 결정합니다. 데이터베이스나 API를 호출하기 전에 모든 도구 실행의 시작 부분에서 권한을 확인합니다.
TOOL_REQUIRED_SCOPES = {
'list_products': ['read:products'],
'search_products': ['read:products'],
'create_order': ['write:orders'],
'delete_order': ['admin:orders']
}
def check_authorization(tool_name: str, token_payload: dict):
'''Raise ValueError if user lacks required scope for the tool.'''
required = TOOL_REQUIRED_SCOPES.get(tool_name, [])
if not required:
return # No scope required
user_scopes = set(token_payload.get('scope', '').split())
missing = [s for s in required if s not in user_scopes]
if missing:
raise ValueError(
f'Access denied. Tool "{tool_name}" requires scopes: {missing}. '
f'Your token has: {list(user_scopes)}'
)
# In call_tool handler:
# check_authorization(name, current_user_token)
# ... then execute the tool입력 검증과 삽입 공격 방지
모든 도구 입력은 결국 LLM이 생성한 문자열이므로 신뢰할 수 없는 값으로 취급해야 합니다. 사용하기 전에 모든 입력을 예상한 형식과 패턴에 따라 검증합니다. 특히 SQL 삽입(매개변수화된 쿼리를 사용하고 문자열 보간 SQL은 절대 사용하지 않음), 명령 삽입(사용자 입력을 셸 명령에 절대 전달하지 않음), 경로 순회(파일 경로를 정규화하고 검증함)를 방어해야 합니다.
import re
from pathlib import Path
BASE_DATA_DIR = Path('/data/mcp-files')
def safe_file_path(user_input: str) -> Path:
'''Validate and normalize a file path to prevent traversal attacks.'''
# Remove any path traversal sequences
clean = re.sub(r'\.\./', '', user_input)
clean = re.sub(r'\.\.\\\\', '', clean)
path = (BASE_DATA_DIR / clean).resolve()
# Ensure the resolved path is still within the allowed base directory
if not str(path).startswith(str(BASE_DATA_DIR)):
raise ValueError(f'Path traversal detected: {user_input}')
return path
def safe_identifier(value: str) -> str:
'''Validate a database identifier (table/column name).'''
if not re.match(r'^[a-z_][a-z0-9_]{0,63}$', value, re.IGNORECASE):
raise ValueError(f'Invalid identifier: {value}')
return value도구 호출 속도 제한
반복 실행되는 LLM 에이전트는 비용이 많이 드는 도구를 분당 수백 번 호출하여 데이터베이스 연결, 타사 API 할당량 또는 컴퓨팅 예산을 고갈시킬 수 있습니다. Redis에서 토큰 버킷 알고리즘을 사용해 사용자별 속도 제한을 구현합니다. 제한을 초과하는 도구 호출은 에이전트가 기다려야 한다는 것을 알 수 있도록 설명적인 오류와 함께 거부합니다.
import redis
import time
r = redis.Redis.from_url('redis://localhost:6379')
def check_rate_limit(user_id: str, tool_name: str, limit: int = 60, window: int = 60) -> None:
'''Allow at most `limit` calls per `window` seconds per user per tool.'''
key = f'rate:{user_id}:{tool_name}'
pipe = r.pipeline()
pipe.incr(key)
pipe.expire(key, window)
count, _ = pipe.execute()
if count > limit:
retry_after = r.ttl(key)
raise ValueError(
f'Rate limit exceeded for {tool_name}. '
f'Limit: {limit} calls/{window}s. '
f'Retry after {retry_after} seconds.'
)최소 권한 원칙
MCP 서버의 모든 계층에 최소 권한 원칙을 적용합니다. 데이터베이스 사용자는 서버에 필요한 테이블에서 SELECT만 수행할 수 있어야 합니다. 서버 프로세스는 root가 아닌 OS 사용자로 실행해야 합니다. 도구는 필요한 권한만 요청해야 합니다. API 키에는 용도에 필요한 최소 범위만 부여해야 합니다. 부여하지 않은 각 권한은 공격이 성공하지 못하도록 막는 잠재적인 방어 수단입니다.
-- PostgreSQL: Create a dedicated read-only database user for your MCP server
CREATE ROLE mcp_reader LOGIN PASSWORD 'strong_random_password';
-- Grant SELECT on only the tables the server needs
GRANT SELECT ON products, categories, public_content TO mcp_reader;
-- Explicitly deny access to sensitive tables
REVOKE ALL ON users, api_keys, payment_methods FROM mcp_reader;
-- Never grant: INSERT, UPDATE, DELETE, TRUNCATE, or DDL permissionsTLS와 전송 보안
원격 MCP 서버는 전송 중인 데이터를 보호하기 위해 TLS를 사용해야 합니다. 서버가 HTTPS 연결만 허용하도록 구성합니다. 운영 환경에서는 역방향 프록시(nginx, Caddy)를 사용해 TLS 종료를 처리하고, 자동 갱신 기능으로 인증서를 최신 상태로 유지합니다(Certbot을 통한 Let's Encrypt 또는 Caddy의 기본 제공 ACME 지원).
# Example Caddyfile for TLS-terminating MCP server at a subdomain:
#
# mcp.yourcompany.com {
# reverse_proxy localhost:8080
# encode gzip
# tls internal # Use Let's Encrypt in production
# header {
# Strict-Transport-Security 'max-age=31536000; includeSubDomains'
# X-Content-Type-Options nosniff
# X-Frame-Options DENY
# }
# }MCP 리소스를 통한 프롬프트 삽입
미묘한 공격 경로가 있습니다. MCP 서버가 외부 원천(웹 페이지, 사용자가 업로드한 파일, 신뢰할 수 없는 데이터베이스)에서 콘텐츠를 읽어 도구 결과로 반환하면 공격자가 해당 콘텐츠에 적대적인 지침을 삽입할 수 있습니다. 모델은 가져온 데이터에 숨겨진 지침을 따를 수 있으며, 이를 간접 프롬프트 삽입이라고 합니다. 가져온 콘텐츠를 정제하고 신뢰할 수 없는 원시 텍스트를 도구 출력으로 직접 반환하지 않습니다.
import re
def sanitize_for_mcp_output(text: str) -> str:
'''Remove patterns that look like instructions to the LLM.'''
# Remove common injection patterns
dangerous_patterns = [
r'ignore previous instructions',
r'ignore all prior instructions',
r'system:',
r'<\|.*?\|>', # Special tokens
r'\[INST\]',
r'<s>',
]
for pattern in dangerous_patterns:
text = re.sub(pattern, '[FILTERED]', text, flags=re.IGNORECASE)
return text[:10000] # Also cap length to prevent context stuffing보안 감사와 모니터링
인증 성공 및 실패, 속도 제한 위반, 권한 부여 거부, 유효성 검사 오류, 비정상적인 액세스 패턴 등 보안과 관련된 모든 이벤트를 기록하세요. 다음 상황에 대한 알림을 설정하세요. 여러 인증 시도가 실패하는 경우(무차별 대입 공격), 한 사용자가 파괴적인 도구를 빠르게 연속 호출하는 경우, 입력이 매우 큰 도구 호출이 발생하는 경우입니다. 감사 로그를 정기적으로 검토하고 이상 징후 탐지를 자동화하세요.
빠른 확인
MCP 보안 및 인증 개념에 대한 이해도를 확인해 보세요.
학습 내용 요약
이 학습에서는 다음을 배웠습니다. 원격 MCP 서버는 모든 요청에 OAuth 2.0 또는 API 키 인증이 필요합니다. 범위 기반 권한 부여는 인증된 각 사용자가 호출할 수 있는 도구를 제어합니다. 또한 인젝션 공격과 경로 순회 공격을 방지하려면 모든 도구 입력을 검증해야 합니다. 이것으로 MCP 모듈을 마칩니다. 다음으로는 정밀도가 높은 RAG 검색을 위한 고급 분할 전략을 살펴보겠습니다.
자주 묻는 질문
“MCP 보안과 인증” 강의는 무료인가요?
네 — “MCP 보안과 인증” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Engineering Academy 강의 전체를 잠금 해제할 수 있습니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“MCP 보안과 인증”에서 뭘 배우나요?
OAuth 2.0 토큰을 사용해 MCP 서버에 인증을 추가하고, 삽입 공격을 방지하도록 입력 검증을 구현하며, 도구 권한에 최소 권한 원칙을 적용합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Engineering Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
AI Engineering Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 AI Engineering Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“MCP 보안과 인증” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 AI Engineering Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 AI Engineering Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.