Blog
HomePostsTagsCategories
© 2026 Hyeonseok An. All rights reserved.
GitHubEmailInstagram
Home
Security
웹 보안 완벽 가이드: CSP(Content Security Policy) 마스터하기

웹 보안 완벽 가이드: CSP(Content Security Policy) 마스터하기

August 21, 2025
Loading...
CSPWebSecurityContent Security PolicyXSSHTTP HeaderNext.jsFront-end SecurityFront-endThird-party integrationAdFitSecurity Header
Cover image for 웹 보안 완벽 가이드: CSP(Content Security Policy) 마스터하기

1. CSP 이해하기: 웹 보안의 새로운 패러다임

CSP가 해결하는 핵심 문제

Content Security Policy는 현대 웹 애플리케이션이 직면한 가장 위험한 공격 중 하나인 XSS(Cross-Site Scripting)를 효과적으로 방어하는 HTTP 보안 헤더입니다. 전통적인 서버 사이드 필터링만으로는 한계가 있던 클라이언트 사이드 보안을 브라우저 레벨에서 강화합니다.
CSP의 핵심 아이디어는 간단하면서도 강력합니다. 웹 페이지가 로드할 수 있는 리소스의 출처를 명시적으로 정의함으로써, 악성 스크립트나 스타일시트가 실행되는 것을 원천적으로 차단하는 것입니다.

작동 원리와 보안 메커니즘

CSP는 화이트리스트 기반의 보안 모델을 사용합니다. 개발자가 신뢰할 수 있는 리소스 출처를 명시하면, 브라우저는 해당 정책을 엄격하게 적용하여 정의되지 않은 출처의 리소스를 차단합니다. 이러한 접근 방식은 기존의 블랙리스트 기반 방어와 비교해 훨씬 강력하고 예측 가능한 보안을 제공합니다.
참고 자료:
  • MDN CSP 가이드
  • Google Web Fundamentals - CSP

2. 핵심 디렉티브 완벽 분석

기본 리소스 디렉티브

CSP 정책의 핵심은 다양한 디렉티브들입니다. 각 디렉티브는 특정 타입의 리소스에 대한 로딩 정책을 정의합니다.
default-src는 모든 리소스 타입의 기본 정책을 설정하는 폴백 디렉티브입니다. 다른 구체적인 디렉티브가 정의되지 않은 경우, 이 값이 적용됩니다. 예를 들어, default-src 'self'로 설정하면 모든 리소스는 현재 도메인에서만 로드됩니다.
script-src는 JavaScript 실행에 대한 정책을 정의합니다. 이는 XSS 공격 방어의 핵심이 되는 디렉티브로, 인라인 스크립트와 eval() 함수 사용을 제한할 수 있습니다.
style-src는 CSS 로딩과 인라인 스타일에 대한 정책을 관리합니다. 스타일 인젝션 공격을 방지하는 데 중요한 역할을 합니다.

고급 보안 디렉티브

frame-ancestors는 현재 페이지를 iframe으로 embedded할 수 있는 부모 페이지를 제한합니다. 클릭재킹(Clickjacking) 공격을 방지하는 데 효과적입니다.
form-action은 form 태그의 action 속성에서 허용되는 URL을 제한합니다. 이를 통해 사용자 데이터가 악성 서버로 전송되는 것을 방지할 수 있습니다.

값 옵션의 실용적 활용

CSP 디렉티브 값들은 각각 다른 보안 수준과 유연성을 제공합니다:
  • 'self': 현재 도메인만 허용하는 가장 안전한 기본 옵션
  • 'none': 모든 리소스를 차단하는 가장 엄격한 설정
  • 'unsafe-inline'과 'unsafe-eval': 편의성을 위해 보안을 완화하는 옵션들로, 가능한 한 사용을 피해야 합니다
도메인 지정 시에는 프로토콜, 포트, 경로까지 구체적으로 명시할 수 있습니다. 와일드카드 서브도메인(*.example.com)을 사용할 때는 보안과 편의성 사이의 균형을 신중히 고려해야 합니다.

3. 환경별 전략: Development vs Production

Development 환경에서의 점진적 접근

개발 환경에서는 Content-Security-Policy-Report-Only 헤더를 사용하는 것이 핵심 전략입니다. 이 모드에서는 정책 위반이 발생해도 리소스 로딩을 차단하지 않고, 콘솔에 경고 메시지만 출력합니다.
// 개발 환경 설정 예시 const developmentCSP = { key: 'Content-Security-Policy-Report-Only', value: "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" }
이러한 접근 방식의 장점은 기존 코드를 점진적으로 CSP 호환 상태로 개선할 수 있다는 것입니다. 개발자는 애플리케이션의 정상적인 동작을 유지하면서 보안 정책 위반 사항을 파악하고 수정할 수 있습니다.

Production 환경에서의 엄격한 적용

프로덕션 환경에서는 Content-Security-Policy 헤더를 사용하여 정책을 엄격하게 적용합니다. 모든 위반 사항은 실제로 차단되며, 이는 실질적인 보안 향상으로 이어집니다.
// 프로덕션 환경 설정 예시 const productionCSP = { key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'" }
프로덕션 배포 전에는 충분한 테스트를 통해 모든 기능이 정상 동작하는지 확인해야 합니다. 특히 서드파티 서비스나 CDN을 사용하는 경우 해당 도메인들이 적절히 화이트리스트에 포함되었는지 검증이 필요합니다.

4. Next.js 프로젝트 실전 적용

next.config.ts 설정 최적화

Next.js 프로젝트에서 CSP를 구현하는 가장 효과적인 방법은 next.config.ts 파일의 headers 함수를 활용하는 것입니다. 환경에 따른 조건부 설정을 통해 개발과 프로덕션 환경을 구분할 수 있습니다.
// next.config.ts const isDevelopment = process.env.NODE_ENV === 'development' const cspValue = [ "default-src 'self'", "script-src 'self' <https://t1.daumcdn.net> <http://t1.daumcdn.net>", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "font-src 'self'", "connect-src 'self'", "frame-src 'none'" ].join('; ') export default { async headers() { return [{ source: '/(.*)', headers: [{ key: isDevelopment ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy', value: cspValue }] }] } }
이 설정의 핵심은 환경 변수를 활용한 조건부 헤더 적용입니다. 개발 중에는 리포트 모드로 동작하여 개발 생산성을 유지하고, 프로덕션에서는 엄격한 정책을 적용하여 보안을 강화합니다.

동적 CSP 생성과 nonce 활용

더 고급 구현에서는 페이지별로 동적 CSP를 생성하거나 nonce를 활용할 수 있습니다. Nonce는 서버에서 생성한 일회성 토큰으로, 특정 인라인 스크립트나 스타일의 실행을 허용하는 안전한 방법입니다.
// pages/_document.tsx에서 nonce 활용 예시 import { Html, Head, Main, NextScript } from 'next/document' import { randomBytes } from 'crypto' export default function Document() { const nonce = randomBytes(16).toString('base64') return ( <Html> <Head nonce={nonce} /> <body> <Main /> <NextScript nonce={nonce} /> </body> </Html> ) }
참고 자료:
  • Next.js Security Headers
  • Vercel CSP 가이드

5. 서드파티 서비스 통합의 현실적 해결책

AdFit 통합 시 발생하는 실무 문제

실제 프로젝트에서 AdFit과 같은 광고 서비스를 통합할 때 가장 흔히 마주치는 문제는 HTTP/HTTPS 혼용 이슈입니다. AdFit 스크립트가 때로는 HTTP로 로드되기 때문에, HTTPS만 허용하는 엄격한 CSP 정책에서는 차단됩니다.
// AdFit 통합을 위한 CSP 설정 const adFitCompatibleCSP = [ "default-src 'self'", "script-src 'self' <https://t1.daumcdn.net> <http://t1.daumcdn.net> <https://adfit.kakao.com>", "style-src 'self' 'unsafe-inline' <https://t1.daumcdn.net>", "img-src 'self' data: https: http:", "frame-src <https://adfit.kakao.com>", "connect-src 'self' <https://track.tiara.kakao.com>" ].join('; ')
이러한 설정에서 주목할 점은 HTTP와 HTTPS 도메인을 모두 포함한다는 것입니다. 이는 보안상 이상적이지 않지만, 서드파티 서비스의 제약으로 인한 현실적인 타협점입니다.

서드파티 서비스 화이트리스트 관리 전략

여러 서드파티 서비스를 사용하는 경우, 각 서비스별로 필요한 도메인들을 체계적으로 관리하는 것이 중요합니다. 서비스별로 설정을 모듈화하면 유지보수가 훨씬 쉬워집니다.
// 서비스별 CSP 설정 모듈화 const thirdPartyServices = { adfit: { scripts: ['<https://t1.daumcdn.net>', '<http://t1.daumcdn.net>'], styles: ['<https://t1.daumcdn.net>'], frames: ['<https://adfit.kakao.com>'], connects: ['<https://track.tiara.kakao.com>'] }, googleAnalytics: { scripts: ['<https://www.googletagmanager.com>', '<https://www.google-analytics.com>'], connects: ['<https://www.google-analytics.com>', '<https://analytics.google.com>'] } } // 동적 CSP 생성 함수 function generateCSP(enabledServices: string[]) { const policies = { 'script-src': ["'self'"], 'style-src': ["'self'", "'unsafe-inline'"], 'frame-src': ["'none'"], 'connect-src': ["'self'"] } enabledServices.forEach(service => { const config = thirdPartyServices[service] if (config) { if (config.scripts) policies['script-src'].push(...config.scripts) if (config.styles) policies['style-src'].push(...config.styles) if (config.frames) { if (policies['frame-src'][0] === "'none'") policies['frame-src'] = [] policies['frame-src'].push(...config.frames) } if (config.connects) policies['connect-src'].push(...config.connects) } }) return Object.entries(policies) .map(([directive, sources]) => `${directive} ${sources.join(' ')}`) .join('; ') }
이러한 접근 방식은 새로운 서드파티 서비스를 추가하거나 기존 서비스를 제거할 때 CSP 정책을 쉽게 조정할 수 있게 해줍니다.

6. 보안 모범 사례와 지속적 모니터링

자동화된 CSP 위반 감지 시스템

CSP 위반을 실시간으로 감지하고 대응하기 위해서는 자동화된 모니터링 시스템이 필수입니다. Puppeteer를 활용한 E2E 테스트에서 CSP 위반을 자동으로 감지할 수 있습니다.
// Puppeteer를 활용한 CSP 위반 자동 감지 const puppeteer = require('puppeteer') async function detectCSPViolations(url) { const browser = await puppeteer.launch() const page = await browser.newPage() const violations = [] // CSP 위반 이벤트 리스너 등록 await page.evaluateOnNewDocument(() => { document.addEventListener('securitypolicyviolation', (event) => { window.__cspViolations = window.__cspViolations || [] window.__cspViolations.push({ blockedURI: event.blockedURI, violatedDirective: event.violatedDirective, effectiveDirective: event.effectiveDirective, originalPolicy: event.originalPolicy, sourceFile: event.sourceFile, lineNumber: event.lineNumber }) }) }) await page.goto(url, { waitUntil: 'networkidle0' }) // 페이지에서 발생한 CSP 위반 수집 const cspViolations = await page.evaluate(() => { return window.__cspViolations || [] }) await browser.close() return cspViolations } // 사용 예시 detectCSPViolations('<http://localhost:3000>').then(violations => { if (violations.length > 0) { console.error('CSP violations detected:', violations) process.exit(1) // CI/CD에서 빌드 실패 처리 } else { console.log('No CSP violations found') } })

점진적 정책 강화 전략

CSP는 한 번에 완벽하게 설정하기보다는 점진적으로 강화해나가는 것이 현실적입니다. 다음과 같은 단계별 접근을 권장합니다:
1단계: 기본 정책 수립default-src 'self'를 기본으로 하되, 필요한 서드파티 도메인들을 추가합니다. 이 단계에서는 'unsafe-inline'과 'unsafe-eval'을 임시로 허용할 수 있습니다.
2단계: 인라인 코드 제거 애플리케이션에서 인라인 스크립트와 스타일을 외부 파일로 분리하고, 'unsafe-inline' 정책을 제거합니다. 이 과정에서 nonce나 hash 기반 허용 방식을 도입할 수 있습니다.
3단계: eval() 함수 사용 최소화eval() 함수나 동적 코드 실행을 최소화하고 'unsafe-eval' 정책을 제거합니다. 일부 라이브러리에서 eval을 사용하는 경우, 대안을 찾거나 라이브러리를 교체해야 할 수 있습니다.
4단계: 정책 최적화 불필요한 도메인을 제거하고, 더 구체적인 경로나 서브도메인 제한을 적용하여 정책을 최적화합니다.

통합 보안 헤더 전략

CSP는 다른 보안 헤더들과 함께 사용할 때 더욱 효과적입니다. 포괄적인 웹 보안을 위해 다음과 같은 헤더들을 함께 설정하는 것이 좋습니다:
const securityHeaders = [ { key: 'Content-Security-Policy', value: cspValue }, { key: 'X-Frame-Options', value: 'DENY' // 클릭재킹 방지 }, { key: 'X-Content-Type-Options', value: 'nosniff' // MIME 타입 스니핑 방지 }, { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' // 리퍼러 정보 제한 }, { key: 'X-XSS-Protection', value: '1; mode=block' // XSS 필터링 활성화 }, { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' // HTTPS 강제 }, { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' // 브라우저 API 접근 제한 } ]

지속적 모니터링과 개선

CSP는 한 번 설정하고 끝나는 것이 아니라 지속적인 모니터링과 개선이 필요합니다. 새로운 기능 추가, 서드파티 서비스 변경, 라이브러리 업데이트 등으로 인해 정책 조정이 필요할 수 있습니다.
정기적인 보안 감사를 통해 CSP 정책의 효과성을 검토하고, 보안 위협 동향에 맞춰 정책을 업데이트해야 합니다. 또한 CSP 위반 로그를 분석하여 잠재적인 공격 시도를 탐지하고 대응할 수 있습니다.
참고 도구:
  • CSP Evaluator - Google의 CSP 정책 분석 도구
  • Report URI - CSP 위반 리포트 수집 서비스
  • SecurityHeaders.com - 웹사이트 보안 헤더 검사 도구

결론

CSP는 현대 웹 애플리케이션 보안의 핵심 요소입니다. 올바른 구현과 지속적인 관리를 통해 XSS 공격을 효과적으로 방어하고 전반적인 보안 수준을 크게 향상시킬 수 있습니다.
중요한 것은 완벽한 정책을 처음부터 만들려고 하기보다는, 점진적으로 개선해나가면서 애플리케이션의 특성에 맞는 최적의 보안 정책을 찾아가는 것입니다. 개발팀의 생산성과 보안 사이의 균형을 유지하며, 사용자와 비즈니스 모두에게 가치를 제공하는 것이 CSP 구현의 궁극적인 목표라고 할 수 있습니다.
Tags:CSPWebSecurityContent Security PolicyXSSHTTP HeaderNext.jsFront-end SecurityFront-endThird-party integrationAdFitSecurity Header