سياسة أمان المحتوى (Content Security Policy)
سياسة أمان المحتوى (CSP) مهمة لحماية تطبيق Next.js الخاص بك من تهديدات أمنية مختلفة مثل التنصت عبر المواقع (XSS)، واحتيال النقر (clickjacking)، وهجمات حقن التعليمات البرمجية الأخرى.
باستخدام CSP، يمكن للمطورين تحديد المصادر المسموح بها للمحتوى، والنصوص البرمجية، وملفات الأنماط، والصور، والخطوط، والكائنات، والوسائط (الصوت، الفيديو)، والإطارات المضمنة (iframes)، والمزيد.
أمثلة
القيم العشوائية لمرة واحدة (Nonces)
القيمة العشوائية لمرة واحدة (nonce) هي سلسلة فريدة وعشوائية من الأحرف يتم إنشاؤها لاستخدام لمرة واحدة. تستخدم مع 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
}
بشكل افتراضي، تعمل البرمجية الوسيطة على جميع الطلبات. يمكنك تصفية البرمجية الوسيطة لتشغيلها على مسارات محددة باستخدام 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' },
],
},
],
}
قراءة القيمة العشوائية لمرة واحدة
يمكنك الآن قراءة القيمة العشوائية لمرة واحدة من مكون الخادم (Server Component) باستخدام 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, ''),
},
],
},
]
},
}
سجل الإصدارات
نوصي باستخدام إصدار v13.4.20+
من Next.js للتعامل مع القيم العشوائية لمرة واحدة وتطبيقها بشكل صحيح.