framework/NextJS

๐Ÿ›ก๏ธ Next.js Content Security Policy(CSP) ์„ค์ • ๊ฐ€์ด๋“œ

octo54 2025. 5. 9. 10:53
๋ฐ˜์‘ํ˜•

๐Ÿ›ก๏ธ Next.js Content Security Policy(CSP) ์„ค์ • ๊ฐ€์ด๋“œ

Next.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๊ธฐ ์œ„ํ•ด **Content Security Policy(CSP)**๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
CSP๋Š” XSS(Cross-Site Scripting) ๋ฐ ์ฝ”๋“œ ์ธ์ ์…˜ ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.


โœ… 1. CSP์˜ ๊ธฐ๋ณธ ๊ฐœ๋…

CSP(Content Security Policy)๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค๋ฅผ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ์‹คํ–‰ํ•  ๋•Œ ํ—ˆ์šฉ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋ณด์•ˆ ์ •์ฑ…์ž…๋‹ˆ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ์•…์„ฑ ์ฝ”๋“œ ์‹คํ–‰์„ ๋ฐฉ์ง€ํ•˜๊ณ , ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์†Œ์Šค๋งŒ ํ—ˆ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•ฉ๋‹ˆ๋‹ค.


โœ… 2. Next.js์—์„œ ๊ธฐ๋ณธ CSP ์„ค์ •

Next.js์—์„œ๋Š” next.config.js ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ CSP๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์„ค์ •

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self';",
          },
        ],
      },
    ];
  },
};

์„ค์ • ํ•ด์„

  • default-src 'self': ํ˜„์žฌ ๋„๋ฉ”์ธ์—์„œ๋งŒ ๋ฆฌ์†Œ์Šค๋ฅผ ๋กœ๋“œ
  • script-src 'self': ์Šคํฌ๋ฆฝํŠธ๋„ ํ˜„์žฌ ๋„๋ฉ”์ธ์—์„œ๋งŒ ํ—ˆ์šฉ
  • style-src 'self': ์Šคํƒ€์ผ ์—ญ์‹œ ํ˜„์žฌ ๋„๋ฉ”์ธ์—์„œ๋งŒ ํ—ˆ์šฉ
  • img-src 'self': ์ด๋ฏธ์ง€๋„ ๋™์ผ ๋„๋ฉ”์ธ์—์„œ๋งŒ ๋กœ๋“œ

โœ… 3. ๋™์  ์ฝ˜ํ…์ธ ๋ฅผ ์œ„ํ•œ Nonce ์„ค์ •

์ผ๋ถ€ ๋™์  ์Šคํฌ๋ฆฝํŠธ๋‚˜ ์Šคํƒ€์ผ์„ ํ—ˆ์šฉํ•ด์•ผ ํ•  ๋•Œ๋Š” Nonce๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์ธ๋ผ์ธ ์ฝ˜ํ…์ธ ๋งŒ ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3.1. Nonce ์ƒ์„ฑ ๋ฏธ๋“ค์›จ์–ด ์„ค์ •

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

export function middleware(request: NextRequest) {
  const nonce = crypto.randomBytes(16).toString('base64');
  const csp = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}';
    style-src 'self' 'nonce-${nonce}';
    img-src 'self' data:;
    connect-src 'self';
  `.replace(/\s{2,}/g, ' ').trim();

  const response = NextResponse.next();
  response.headers.set('Content-Security-Policy', csp);
  response.headers.set('x-nonce', nonce);
  return response;
}

3.2. Nonce๋ฅผ ํ™œ์šฉํ•œ ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ ์„ค์ •

// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    const nonce = (this.props as any).nonce;
    return (
      <Html>
        <Head>
          <script nonce={nonce}>console.log('์•ˆ์ „ํ•œ ์Šคํฌ๋ฆฝํŠธ');</script>
        </Head>
        <body>
          <Main />
          <NextScript nonce={nonce} />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

โœ… 4. CSP ์„ค์ • ์ฃผ์˜์‚ฌํ•ญ

๋™์  ๋ฆฌ์†Œ์Šค ๋ฌธ์ œ

๋ฐ˜์‘ํ˜•
  • React Strict Mode: ์ผ๋ถ€ ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ CSP์— ์˜ํ•ด ์ฐจ๋‹จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • External Script: ์™ธ๋ถ€์—์„œ ์ œ๊ณต๋˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋Š” ๋ฐ˜๋“œ์‹œ script-src ์„ค์ •์— ๋„๋ฉ”์ธ์„ ๋ช…์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ณด๊ณ  ์„ค์ •

CSP ์œ„๋ฐ˜ ๋กœ๊ทธ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ์กฐ๊ธฐ์— ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Content-Security-Policy: default-src 'self'; report-uri /csp-report

์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง

CSP ์œ„๋ฐ˜ ๋ณด๊ณ ๋ฅผ ์ˆ˜์ง‘ํ•˜์—ฌ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ „์†ก URL์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


โœ… 5. ์‹ค์ „ ์˜ˆ์ œ

๊ฐ•ํ™”๋œ CSP ์„ค์ •

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: `
              default-src 'self';
              script-src 'self' 'unsafe-inline' https://apis.google.com;
              style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
              img-src 'self' data:;
              connect-src 'self' https://api.example.com;
              font-src 'self' https://fonts.gstatic.com;
              frame-ancestors 'none';
              object-src 'none';
              base-uri 'self';
              form-action 'self';
              upgrade-insecure-requests;
            `.replace(/\s{2,}/g, ' ').trim(),
          },
        ],
      },
    ];
  },
};

ํ•ด์„ค

  • script-src 'unsafe-inline': ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ ํ—ˆ์šฉ (๊ถŒ์žฅํ•˜์ง€ ์•Š์Œ)
  • style-src 'unsafe-inline': ์ธ๋ผ์ธ ์Šคํƒ€์ผ ํ—ˆ์šฉ
  • connect-src https://api.example.com: ์™ธ๋ถ€ API ํ˜ธ์ถœ ํ—ˆ์šฉ
  • font-src https://fonts.gstatic.com: Google Fonts ํ—ˆ์šฉ
  • frame-ancestors 'none': ์™ธ๋ถ€์—์„œ iframe์œผ๋กœ ์‚ฝ์ž… ๋ถˆ๊ฐ€
  • object-src 'none': ํ”Œ๋ž˜์‹œ ๊ฐ™์€ ๊ฐ์ฒด ์‚ฌ์šฉ ๊ธˆ์ง€

โœ… 6. CSP ํ…Œ์ŠคํŠธ ๋ฐ ๋””๋ฒ„๊น…

๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ํ™œ์šฉ

  1. ํŽ˜์ด์ง€๋ฅผ ์—ด๊ณ  F12(๊ฐœ๋ฐœ์ž ๋„๊ตฌ) ํด๋ฆญ
  2. Console ํƒญ์—์„œ CSP ์œ„๋ฐ˜ ๋กœ๊ทธ ํ™•์ธ
  3. Content Security Policy ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ •์ฑ… ์ˆ˜์ •

์œ„๋ฐ˜ ๋กœ๊ทธ ํ™•์ธ

CSP ์œ„๋ฐ˜ ๋ณด๊ณ ์„œ๋ฅผ ์„œ๋ฒ„๋กœ ์ „์†กํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Content-Security-Policy: default-src 'self'; report-uri /csp-report

โœ… ์š”์•ฝ ์ •๋ฆฌ

ํ•ญ๋ชฉ ์„ค์ • ๋ฐฉ๋ฒ• ๋น„๊ณ 

๊ธฐ๋ณธ CSP ์„ค์ • next.config.js์—์„œ ์„ค์ • ๋ชจ๋“  ํŽ˜์ด์ง€์— ์ ์šฉ
Nonce ํ™œ์šฉ ๋ฏธ๋“ค์›จ์–ด์—์„œ ๋™์  ์ƒ์„ฑ ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ ํ—ˆ์šฉ
์œ„๋ฐ˜ ๋ณด๊ณ  ์„ค์ • report-uri๋กœ ๋กœ๊ทธ ์ „์†ก ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง ๊ฐ€๋Šฅ
ํ…Œ์ŠคํŠธ ๋ฐ ๋””๋ฒ„๊น… ๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ํ™œ์šฉ CSP ์˜ค๋ฅ˜ ํŒŒ์•… ์šฉ์ด

 

Next.js, Content Security Policy, CSP, ์›น ๋ณด์•ˆ, XSS ๋ฐฉ์ง€, nonce, ๋ณด์•ˆ ํ—ค๋”, ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณด์•ˆ, Next.js ๋ณด์•ˆ, ์„œ๋ฒ„ ๋ณด์•ˆ ์„ค์ •