Политика безопасности контента (Content Security Policy)
Политика безопасности контента (CSP) важна для защиты вашего приложения Next.js от различных угроз безопасности, таких как межсайтовый скриптинг (XSS), кликджекинг и другие атаки с внедрением кода.
Используя CSP, разработчики могут указать, какие источники разрешены для загрузки контента, скриптов, таблиц стилей, изображений, шрифтов, объектов, медиа (аудио, видео), iframe и других ресурсов.
Примеры
Нонсы (Nonces)
Нонс — это уникальная случайная строка символов, созданная для однократного использования. Он используется вместе с CSP для выборочного разрешения выполнения определённых встроенных скриптов или стилей, обходя строгие директивы CSP.
Зачем использовать нонс?
Хотя CSP предназначены для блокировки вредоносных скриптов, существуют легитимные сценарии, когда встроенные скрипты необходимы. В таких случаях нонсы предоставляют способ разрешить выполнение этих скриптов, если они содержат правильный нонс.
Добавление нонса с помощью Middleware
Middleware позволяет добавлять заголовки и генерировать нонсы перед отрисовкой страницы.
Каждый раз при просмотре страницы должен генерироваться новый нонс. Это означает, что вы должны использовать динамическую отрисовку для добавления нонсов.
Например:
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 выполняется для всех запросов. Вы можете фильтровать выполнение Middleware для определённых путей, используя matcher
.
Рекомендуем исключать из обработки префетчи (из 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' },
],
},
],
}
Чтение нонса
Теперь вы можете прочитать нонс из Серверного Компонента, используя headers
:
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}
/>
)
}
Без нонсов
Для приложений, которые не требуют нонсов, вы можете установить заголовок CSP непосредственно в файле next.config.js
:
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, ''),
},
],
},
]
},
}
История версий
Рекомендуем использовать Next.js версии v13.4.20+
для корректной обработки и применения нонсов.