Content Security Policy
Content Security Policy (CSP) (opens in a new tab)는 Next.js 애플리케이션을 크로스 사이트 스크립팅(XSS), 클릭재킹 및 기타 코드 주입 공격과 같은 다양한 보안 위협으로부터 보호하는 데 중요합니다.
CSP를 사용하여 개발자는 콘텐츠 소스, 스크립트, 스타일시트, 이미지, 폰트, 객체, 미디어(오디오, 비디오), iframes 등을 허용할 출처를 지정할 수 있습니다.
Examples
Nonces
nonce (opens in a new tab)는 일회용으로 생성된 고유한 임의 문자열입니다. 이는 CSP와 함께 사용되어 특정 인라인 스크립트나 스타일을 선택적으로 실행할 수 있도록 하여 엄격한 CSP 지시를 우회할 수 있습니다.
Why use a nonce?
CSP는 악성 스크립트를 차단하도록 설계되었지만, 인라인 스크립트가 필요한 합법적인 시나리오가 존재합니다. 이러한 경우 nonce를 사용하여 올바른 nonce를 가진 스크립트가 실행될 수 있도록 합니다.
Adding a nonce with Middleware
Middleware를 사용하여 페이지가 렌더링되기 전에 헤더를 추가하고 nonce를 생성할 수 있습니다.
페이지가 조회될 때마다 새로운 nonce가 생성되어야 합니다. 이는 nonce를 추가하려면 동적 렌더링을 사용해야 함을 의미합니다.
예시:
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// 줄 바꿈 및 공백 제거
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, ' ')
.trim()
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-nonce', nonce)
requestHeaders.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue,
)
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
})
response.headers.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue,
)
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// 줄 바꿈 및 공백 제거
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, ' ')
.trim()
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-nonce', nonce)
requestHeaders.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue,
)
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
})
response.headers.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue,
)
return response
}
기본적으로 Middleware는 모든 요청에서 실행됩니다. matcher
를 사용하여 특정 경로에서만 Middleware를 실행하도록 필터링할 수 있습니다.
next/link
의 사전 로드 및 CSP 헤더가 필요 없는 정적 자산의 매칭을 무시하는 것이 좋습니다.
export const config = {
matcher: [
/*
* 다음으로 시작하는 요청 경로를 제외한 모든 요청 경로를 매칭합니다:
* - api (API 라우트)
* - _next/static (정적 파일)
* - _next/image (이미지 최적화 파일)
* - favicon.ico (파비콘 파일)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
export const config = {
matcher: [
/*
* 다음으로 시작하는 요청 경로를 제외한 모든 요청 경로를 매칭합니다:
* - api (API 라우트)
* - _next/static (정적 파일)
* - _next/image (이미지 최적화 파일)
* - favicon.ico (파비콘 파일)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
Reading the nonce
이제 headers
를 사용하여 서버 컴포넌트에서 nonce를 읽을 수 있습니다:
import { headers } from 'next/headers'
import Script from 'next/script'
export default function Page() {
const nonce = headers().get('x-nonce')
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}
import { headers } from 'next/headers'
import Script from 'next/script'
export default function Page() {
const nonce = headers().get('x-nonce')
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}
Without Nonces
nonce가 필요 없는 애플리케이션의 경우 next.config.js
파일에서 CSP 헤더를 직접 설정할 수 있습니다:
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
],
},
]
},
}
Version History
nonce를 올바르게 처리하고 적용하려면 Next.js의 v13.4.20+
버전을 사용하는 것이 좋습니다.