NestJS CORS

๊ถŒํƒœํ˜•ยท2023๋…„ 4์›” 28์ผ
2

NestJS ์—ฐ์Šต

๋ชฉ๋ก ๋ณด๊ธฐ
13/19
post-thumbnail

๐Ÿ˜€ํ•„์ž๋Š” ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋„๋ฉ”์ธ์˜ ์˜ค๋ฆฌ์ง„์ด ๋‹ค๋ฅด๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋กœ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฃผ๊ณ ๋ฐ›๊ธฐ์œ„ํ•ด์„œ ๋ฐ˜๋“œ์‹œ CORS์„ค์ •์„ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

NestJS์—์„œ๋Š” CORS(Cross-Origin Resource Sharing) ์„ค์ •์„ ํ†ตํ•ด SOP์— ํ•ด๋‹นํ•˜์ง€ ์•Š๋Š” ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ›๊ธฐ์œ„ํ•œ ๋ฐฉ๋ฒ•์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ œ๊ณตํ•œ๋‹ค.

NestJS์˜ ๊ณต์‹ ํŽ˜์ด์ง€์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋“ค๋กœ CORS์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ œ์‹œํ•œ๋‹ค.

const app = await NestFactory.create(AppModule);
app.enableCors();
await app.listen(3000);
//--------------------------์œ„ ๋˜๋Š” ์•„๋ž˜ ๋ฐฉ๋ฒ•
const app = await NestFactory.create(AppModule, { cors: true });
await app.listen(3000);

๋‘ ๊ฐ€์ง€์˜ ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ•ด ์ฃผ๋ฉฐ, ์ด ๋ฐฉ๋ฒ•๋“ค์€ ๊ธฐ๋ณธ์ ์œผ๋กœ Express corsํŒจํ‚ค์ง€๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

์œ„์˜ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• ๋ชจ๋‘ ๊ธฐ๋ณธ๊ฐ’์„ ์ ์šฉ์‹œํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ์˜ต์…˜์ด ์–ด๋–ป๊ฒŒ ์„ค์ •๋˜๋Š”์ง€ ์•Œ ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

์œ„์˜ ๋ฐฉ๋ฒ•๋Œ€๋กœ CORS๋ฅผ ์„ค์ •ํ•˜๋ฉด ์„ค์ •๋œ ๊ธฐ๋ณธ์˜ต์…˜(default)์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false,
  "optionsSuccessStatus": 204
}

ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ์˜ต์…˜๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ค ์˜ต์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ด์•ผ ๊ฒ ๋‹ค.

.enableCors()ํ•จ์ˆ˜๋ฅผ ctrl+click์œผ๋กœ step-inํ•ด์„œ option์„ ์ฐพ์•„๊ฐ€๋ณด๋ฉด CorsOption์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

export interface CorsOptions {
    /**
     * Configures the `Access-Control-Allow-Origins` CORS header. 
     See [here for more detail.](https://github.com/expressjs/cors#configuration-options)
    ์–ด๋–ค ์˜ค๋ฆฌ์ง„๊ณผ ์—ฐ๊ฒฐ์„ ํ—ˆ์šฉํ•  ๊ฒƒ์ธ๊ฐ€? ๊ธฐ๋ณธ๊ฐ’ true or "*" */
    // type StaticOrigin = boolean | string | RegExp | (string | RegExp)[]
    // type CustomOrigin = (requestOrigin: string, callback: (err: Error | null, origin?: StaticOrigin) => void) => void
    origin?: StaticOrigin | CustomOrigin;
    /**
     * Configures the Access-Control-Allow-Methods CORS header.
     ์–ด๋–ค ๋ฉ”์†Œ๋“œ์˜ ์—ฐ๊ฒฐ์„ ํ—ˆ์šฉํ•  ๊ฒƒ์ธ๊ฐ€?*/
    methods?: string | string[];
    /**
     * Configures the Access-Control-Allow-Headers CORS header.
     ์–ด๋–ค ํ—ค๋”๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ํ•  ๊ฒƒ์ธ๊ฐ€?*/
    allowedHeaders?: string | string[];
    /**
     * Configures the Access-Control-Expose-Headers CORS header.
     ๋ธŒ๋ผ์šฐ์ €์—์„œ ์–ด๋–ค ํ•ด๋”๋ฅผ ๋…ธ์ถœํ•  ๊ฒƒ์ธ๊ฐ€?*/
    exposedHeaders?: string | string[];
    /**
     * Configures the Access-Control-Allow-Credentials CORS header.
     ์ฟ ํ‚ค, ์ธ์ฆ ํ—ค๋” ๋“ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•  ๊ฒƒ์ธ๊ฐ€?*/
    credentials?: boolean;
    /**
     * Configures the Access-Control-Max-Age CORS header.
     CORS ํ”„๋ฆฌํ”Œ๋ผ์ดํŠธ ์š”์ฒญ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•  ์‹œ๊ฐ„์„ ๋ช‡์œผ๋กœ ์„ค์ •ํ•  ๊ฒƒ์ธ๊ฐ€?*/
    maxAge?: number;
    /**
     * Whether to pass the CORS preflight response to the next handler.
     CORS ํ”„๋ฆฌํ”Œ๋ผ์ดํŠธ ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต์ด ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋กœ ์ „๋‹ฌ๋˜๋„๋ก ํ•  ๊ฒƒ์ธ๊ฐ€? ๊ธฐ๋ณธ๊ฐ’ false*/
    preflightContinue?: boolean;
    /**
     * Provides a status code to use for successful OPTIONS requests.
     ์„ฑ๊ณต์ ์ธ OPTIONS ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์–ด๋–ค๊ฒƒ์„ ๋ณด๋‚ผ ๊ฒƒ์ธ๊ฐ€? ๊ธฐ๋ณธ๊ฐ’ 204*/
    optionsSuccessStatus?: number;
}

๋งŒ์•ฝ ๋‚ด๊ฐ€ ๋ชจ๋“  ๋„๋ฉ”์ธ ์˜ค๋ฆฌ์ง„์— ๋Œ€ํ•ด์„œ ํ—ˆ์šฉํ•˜๊ณ , Authํ—ค๋”๋ฅผ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‚ฌ์šฉํ•˜๊ณ , ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ๊ฐ€ ๋  ๊ฒƒ์ด๋‹ค.

app.enableCors({
    origin: true,
    credentials: true,
    exposedHeaders: ['Authorization'], // * ์‚ฌ์šฉํ•  ํ—ค๋” ์ถ”๊ฐ€.
  });

๊ทธ๋ ‡๋‹ค๋ฉด ๋ชจ๋“  ์˜ค๋ฆฌ์ง„์ด ์•„๋‹Œ ์šฐ๋ฆฌ ํ”„๋ก ํŠธ์—์„œ ๋ฐฐํฌํ•œ ์„œ๋ฒ„์˜ ์˜ค๋ฆฌ์ง„(https://www.example.shop)๊ณผ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ๋กœ์ปฌ์„œ๋ฒ„์˜ ์˜ค๋ฆฌ์ง„(http://localhost:3330)์— ๋Œ€ํ•ด์„œ๋งŒ ์„ค์ •ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

app.enableCors({
    origin: ['https://www.example.shop', 'http://localhost:3330'],
    credentials: true,
    exposedHeaders: ['Authorization'], // * ์‚ฌ์šฉํ•  ํ—ค๋” ์ถ”๊ฐ€.
  });

๋‘ ๊ฐ€์ง€ origin์„ ์ฒ˜์Œ ์„ค์ •ํ•  ๋•Œ๋Š” port์˜ ๊ตฌ๋ถ„์ฒ˜๋Ÿผ process.env.port | 3300๊ณผ ๊ฐ™์ด ์“ฐ๋Š” ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•ด์„œ ์ž‘์„ฑํ•ด ๋ณด์•˜์ง€๋งŒ ๋™์ž‘ํ•˜์ง€ ์•Š์•„ ํ™•์ธํ•ด๋ณด๋‹ˆ |์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์—ˆ๋‹ค. origin์˜ต์…˜์˜ StaticOrigin์€ ์œ„ ์„ค์ •์˜ ์ฃผ์„๊ณผ ๊ฐ™์ด boolean | string | RegExp | (string | RegExp)[]์˜ ํƒ€์ž…์„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์œ„์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ํ•„์š”ํ•œ ๋„๋ฉ”์ธ์„ ๋ฐฐ์—ด์˜ ํ˜•ํƒœ๋กœ ๋„ฃ์–ด์ฃผ์–ด์•ผ ์›ํ•˜๋Š” ๋„๋ฉ”์ธ๋“ค์„ ์„ค์ •ํ•  ์ˆ˜์žˆ๋‹ค.

profile
22๋…„ 12์›” ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•œ ์‹ ์ž… ๊ฐœ๋ฐœ์ž โ€˜๊ถŒํƒœํ˜•โ€™์ž…๋‹ˆ๋‹ค. ํฌ์ŠคํŒ… ํ•˜๋‚˜ํ•˜๋‚˜ ๋‚ด๊ฐ€ ๋‹ค์‹œ๋ณด๊ธฐ ์œ„ํ•ด ์“ฐ๋Š” ๊ฒƒ์ด์ง€๋งŒ, ๋‹ค๋ฅธ ๋ถ„๋“ค์—๊ฒŒ๋„ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿ’ฏ์ปฌ๋ŸฌํฐํŠธ๊ฐ€ ์ž˜ ์•ˆ๋ณด์ด์‹ค ๊ฒฝ์šฐ ๐ŸŒ™๋‹คํฌ๋ชจ๋“œ๋ฅผ ์ด์šฉํ•ด์ฃผ์„ธ์š”.๐Ÿ˜€ ์ง€์ ๊ณผ ์ฐธ๊ฒฌ์€ ์–ธ์ œ๋‚˜ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ๋งŽ์€ ๋Œ“๊ธ€ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

1๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2024๋…„ 3์›” 25์ผ

์ž˜ ๋ณด์•˜์Šต๋‹ˆ๋‹ค.
export interface CorsOptions ์˜ ์ฃผ์„๋ถ€๋ถ„๋“ค์€ ์ง์ ‘ ํ•œ๊ธ€๋กœ ๋ฐ”๊พธ์‹ ๊ฑด๊ฐ€์š”??

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ