**CORS(Cross-Origin Resource Sharing)**๋ HTTP ํค๋ ๊ธฐ๋ฐ ๋ฉ์ปค๋์ฆ์ผ๋ก, ์๋ฒ๊ฐ ์์ ์ ์ถ์ฒ(origin)์ ๋ค๋ฅธ ์ถ์ฒ์์ ๋ฆฌ์์ค ์ ๊ทผ์ ํ์ฉํ ์ง ๋ช ์์ ์ผ๋ก ์ง์ ํ ์ ์๋๋ก ํฉ๋๋ค. ๋ธ๋ผ์ฐ์ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก Same-Origin Policy(๋์ผ ์ถ์ฒ ์ ์ฑ )๋ฅผ ์ ์ฉํ์ฌ ๋ค๋ฅธ ์ถ์ฒ๋ก์ HTTP ์์ฒญ์ ์ฐจ๋จํ๋๋ฐ, CORS๋ ์ด๋ฅผ ์์ ํ๊ฒ ์ํํ๋ ํ์คํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
**์ถ์ฒ(Origin)**๋ ํ๋กํ ์ฝ + ๋๋ฉ์ธ + ํฌํธ์ ์กฐํฉ์ ๋๋ค:
https://example.com:443โ ํ๋์ ์ถ์ฒhttp://example.com:80โ ๋ค๋ฅธ ์ถ์ฒ (ํ๋กํ ์ฝ ๋ค๋ฆ)https://api.example.com:443โ ๋ค๋ฅธ ์ถ์ฒ (๋๋ฉ์ธ ๋ค๋ฆ)
ํด๋น ๊ฐ๋ ์ด ํ์ํ ์ด์
- Same-Origin Policy์ ์ ์ฝ ๊ทน๋ณต: ๋ธ๋ผ์ฐ์ ๋ ๋ณด์์ ์ํด ๋ค๋ฅธ ์ถ์ฒ์ ๋ฆฌ์์ค ์ ๊ทผ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฐจ๋จํฉ๋๋ค. CORS๋ฅผ ํตํด ์ ๋ขฐํ ์ ์๋ ์ถ์ฒ์ ํํด ์ ๊ทผ์ ํ์ฉํ ์ ์์ต๋๋ค
- API ์๋ฒ์ ํด๋ผ์ด์ธํธ ๋ถ๋ฆฌ: ์ต๊ทผ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ํ๋ก ํธ์๋(
https://app.example.com)์ ๋ฐฑ์๋ API(https://api.example.com)๋ฅผ ๋ณ๋ ๋๋ฉ์ธ์์ ์ด์ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. CORS ์์ด๋ ์ด๋ฌํ ๊ตฌ์กฐ๊ฐ ๋ถ๊ฐ๋ฅํฉ๋๋ค - ์จ๋ํํฐ API ํตํฉ: ์ธ๋ถ API๋ฅผ ๋ธ๋ผ์ฐ์ ์์ ์ง์ ํธ์ถํ ๋ CORS ์ค์ ์ด ํ์ํฉ๋๋ค (Google Maps API, ๊ฒฐ์ API ๋ฑ)
AS-IS: Same-Origin Policy๋ก ์ธํ ์ฐจ๋จ
sequenceDiagram autonumber participant Browser participant Frontend as Frontend<br/>(https://app.example.com) participant API as API Server<br/>(https://api.example.com) Browser->>Frontend: ํ์ด์ง ๋ก๋ Frontend->>Browser: fetch('https://api.example.com/data') Browser->>API: GET /data<br/>Origin: https://app.example.com API->>Browser: 200 OK (CORS ํค๋ ์์) Browser->>Frontend: โ CORS ์๋ฌ<br/>"Access-Control-Allow-Origin header missing" Note over Browser,API: Same-Origin Policy๋ก ์ธํด ์๋ต ์ฐจ๋จ
TO-BE: CORS ํค๋๋ก ์ ๊ทผ ํ์ฉ
sequenceDiagram autonumber participant Browser participant Frontend as Frontend<br/>(https://app.example.com) participant API as API Server<br/>(https://api.example.com) Browser->>Frontend: ํ์ด์ง ๋ก๋ Frontend->>Browser: fetch('https://api.example.com/data') Browser->>API: GET /data<br/>Origin: https://app.example.com API->>Browser: 200 OK<br/>Access-Control-Allow-Origin: https://app.example.com Browser->>Frontend: โ ์๋ต ์ ๋ฌ ์ฑ๊ณต Note over Browser,API: CORS ํค๋๋ก ์ ๊ทผ ํ์ฉ
CORS ๋์ ์๋ฆฌ
CORS๋ ๋ค์๊ณผ ๊ฐ์ด ์๋ํฉ๋๋ค:
- ๋ธ๋ผ์ฐ์ ๊ฐ cross-origin ์์ฒญ์
Originํค๋๋ฅผ ํฌํจํ์ฌ ์ ์ก - ์๋ฒ๊ฐ
Access-Control-*ํค๋๋ก ํ์ฉ ์ฌ๋ถ๋ฅผ ์๋ต - ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ต ํค๋๋ฅผ ํ์ธํ์ฌ ์ ๊ทผ ํ์ฉ ๋๋ ์ฐจ๋จ
- ํน์ ์์ฒญ์ ๊ฒฝ์ฐ ์ค์ ์์ฒญ ์ ์ **Preflight(์ฌ์ ์์ฒญ)**์ ๋จผ์ ์ ์ก
Simple Request vs Preflighted Request
CORS ์์ฒญ์ ๋ ๊ฐ์ง ์ ํ์ผ๋ก ๋๋ฉ๋๋ค.
Simple Request (๋จ์ ์์ฒญ)
Preflight ์์ด ๋ฐ๋ก ์ค์ ์์ฒญ์ ๋ณด๋ ๋๋ค. ๋ค์ ์กฐ๊ฑด์ ๋ชจ๋ ๋ง์กฑํด์ผ ํฉ๋๋ค:
์กฐ๊ฑด 1: ํ์ฉ๋ HTTP ๋ฉ์๋๋ง ์ฌ์ฉ
GETHEADPOST
์กฐ๊ฑด 2: ํ์ฉ๋ ํค๋๋ง ์ฌ์ฉ (CORS-safelisted headers)
AcceptAccept-LanguageContent-LanguageContent-Type(ํน์ ๊ฐ๋ง ํ์ฉ)Range(๋จ์ผ ๋ฒ์๋ง)
์กฐ๊ฑด 3: Content-Type์ด ๋ค์ ์ค ํ๋
application/x-www-form-urlencodedmultipart/form-datatext/plain
Simple Request ์์
// ํด๋ผ์ด์ธํธ
fetch("https://api.example.com/public-data")
.then(response => response.json())
.then(data => console.log(data));// ๋ธ๋ผ์ฐ์ ๊ฐ ์ ์ก
GET /public-data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Accept: application/json
// ์๋ฒ ์๋ต
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json
{"message": "Hello"}Preflighted Request (์ฌ์ ์์ฒญ)
์ค์ ์์ฒญ ์ ์ OPTIONS ๋ฉ์๋๋ก ์ฌ์ ์์ฒญ์ ๋ณด๋ด ์๋ฒ๊ฐ ํ์ฉํ๋์ง ๋จผ์ ํ์ธํฉ๋๋ค.
Preflight๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ
- GET, HEAD, POST ์ธ์ HTTP ๋ฉ์๋ ์ฌ์ฉ (PUT, DELETE, PATCH ๋ฑ)
- ์ปค์คํ
ํค๋ ํฌํจ (์:
X-Custom-Header,Authorization) - Content-Type์ด ํ์ฉ๋ 3๊ฐ์ง ์ธ์ ๊ฐ (์:
application/json) - XMLHttpRequest.upload์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
- ReadableStream ์ฌ์ฉ
Preflighted Request ์์
// ํด๋ผ์ด์ธํธ
fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json", // preflight ํธ๋ฆฌ๊ฑฐ
"X-Custom-Header": "value" // preflight ํธ๋ฆฌ๊ฑฐ
},
body: JSON.stringify({ name: "Alice" })
});// Step 1: Preflight ์์ฒญ (OPTIONS)
OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-custom-header
// Step 2: Preflight ์๋ต
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Custom-Header
Access-Control-Max-Age: 86400 // 24์๊ฐ ๋์ preflight ๊ฒฐ๊ณผ ์บ์ฑ
// Step 3: ์ค์ ์์ฒญ (preflight ์ฑ๊ณต ์์๋ง)
POST /users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Content-Type: application/json
X-Custom-Header: value
{"name": "Alice"}
// Step 4: ์ค์ ์๋ต
HTTP/1.1 201 Created
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json
{"id": 123, "name": "Alice"}sequenceDiagram autonumber participant Browser participant Server Note over Browser,Server: Preflight ๋จ๊ณ Browser->>Server: OPTIONS /users<br/>Access-Control-Request-Method: POST<br/>Access-Control-Request-Headers: content-type Server->>Browser: 204 No Content<br/>Access-Control-Allow-Methods: POST<br/>Access-Control-Allow-Headers: content-type Note over Browser,Server: ์ค์ ์์ฒญ ๋จ๊ณ Browser->>Server: POST /users<br/>Content-Type: application/json Server->>Browser: 200 OK<br/>Access-Control-Allow-Origin: https://app.example.com
CORS ์ฃผ์ ํค๋
์๋ฒ โ ๋ธ๋ผ์ฐ์ ์๋ต ํค๋
| ํค๋ | ์ค๋ช | ์์ |
|---|---|---|
| Access-Control-Allow-Origin | ํ์ฉํ ์ถ์ฒ ์ง์ | https://app.example.com ๋๋ * |
| Access-Control-Allow-Methods | ํ์ฉํ HTTP ๋ฉ์๋ | POST, GET, OPTIONS |
| Access-Control-Allow-Headers | ํ์ฉํ ์์ฒญ ํค๋ | Content-Type, Authorization |
| Access-Control-Allow-Credentials | ์ธ์ฆ ์ ๋ณด(์ฟ ํค) ํ์ฉ ์ฌ๋ถ | true |
| Access-Control-Max-Age | Preflight ๊ฒฐ๊ณผ ์บ์ฑ ์๊ฐ (์ด) | 86400 (24์๊ฐ) |
| Access-Control-Expose-Headers | JS์์ ์ ๊ทผ ๊ฐ๋ฅํ ์๋ต ํค๋ | X-Custom-Header |
Access-Control-Allow-Origin
๊ฐ์ฅ ์ค์ํ ํค๋๋ก, ์ด๋ค ์ถ์ฒ์ ์์ฒญ์ ํ์ฉํ ์ง ๋ช ์ํฉ๋๋ค.
# ํน์ ์ถ์ฒ๋ง ํ์ฉ
Access-Control-Allow-Origin: https://app.example.com
# ๋ชจ๋ ์ถ์ฒ ํ์ฉ (์ธ์ฆ ์ ๋ณด ์ฌ์ฉ ๋ถ๊ฐ)
Access-Control-Allow-Origin: *์ค์ํ ์ ์ฝ:
- ์ธ์ฆ ์ ๋ณด(์ฟ ํค, Authorization ํค๋)๋ฅผ ํฌํจํ๋ ์์ฒญ์ ๊ฒฝ์ฐ ๋ฐ๋์ ์ ํํ ์ถ์ฒ๋ฅผ ๋ช
์ํด์ผ ํ๋ฉฐ,
*๋ ์ฌ์ฉํ ์ ์์ต๋๋ค
# โ ์ธ์ฆ ์ ๋ณด ํฌํจ ์์ฒญ์์ ์์ผ๋์นด๋ ์ฌ์ฉ - ๋ธ๋ผ์ฐ์ ๊ฐ ์ฐจ๋จ
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
# โ
์ ํํ ์ถ์ฒ ๋ช
์ ํ์
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: trueAccess-Control-Allow-Credentials
์ธ์ฆ ์ ๋ณด(์ฟ ํค, HTTP ์ธ์ฆ, TLS ํด๋ผ์ด์ธํธ ์ธ์ฆ์)๋ฅผ ์์ฒญ์ ํฌํจํ ์ ์๋์ง ์ง์ ํฉ๋๋ค.
Access-Control-Allow-Credentials: true์ด ํค๋๊ฐ true๋ก ์ค์ ๋๋ฉด:
- ํด๋ผ์ด์ธํธ๋
credentials: 'include'์ต์ ์ ์ฌ์ฉํด์ผ ํจ Access-Control-Allow-Origin์ ๋ฐ๋์ ์ ํํ ์ถ์ฒ๋ฅผ ๋ช ์ํด์ผ ํจ (*๋ถ๊ฐ)
Access-Control-Max-Age
Preflight ์์ฒญ์ ๊ฒฐ๊ณผ๋ฅผ ์บ์ฑํ ์๊ฐ(์ด)์ ์ง์ ํฉ๋๋ค. ์บ์ฑ ๊ธฐ๊ฐ ๋์์ ๋์ผํ ์์ฒญ์ ๋ํด preflight๋ฅผ ์๋ตํฉ๋๋ค.
Access-Control-Max-Age: 86400 # 24์๊ฐ (86400์ด)๋ธ๋ผ์ฐ์ โ ์๋ฒ ์์ฒญ ํค๋
| ํค๋ | ์ค๋ช | ์์ |
|---|---|---|
| Origin | ์์ฒญ์ ์ถ์ฒ | https://app.example.com |
| Access-Control-Request-Method | ์ค์ ์์ฒญ์์ ์ฌ์ฉํ HTTP ๋ฉ์๋ (Preflight์์๋ง) | POST |
| Access-Control-Request-Headers | ์ค์ ์์ฒญ์์ ํฌํจํ ํค๋ ๋ชฉ๋ก (Preflight์์๋ง) | Content-Type, X-Custom-Header |
์ธ์ฆ ์ ๋ณด(Credentials) ํฌํจ ์์ฒญ
๊ธฐ๋ณธ์ ์ผ๋ก cross-origin ์์ฒญ์ ์ฟ ํค๋ Authorization ํค๋๋ฅผ ํฌํจํ์ง ์์ต๋๋ค. ์ธ์ฆ ์ ๋ณด๋ฅผ ํฌํจํ๋ ค๋ฉด ํด๋ผ์ด์ธํธ์ ์๋ฒ ๋ชจ๋ ์ค์ ์ด ํ์ํฉ๋๋ค.
ํด๋ผ์ด์ธํธ ์ค์
Fetch API
fetch("https://api.example.com/user-data", {
credentials: "include" // ์ฟ ํค ๋ฐ ์ธ์ฆ ์ ๋ณด ํฌํจ
})
.then(response => response.json())
.then(data => console.log(data));credentials ์ต์ ๊ฐ:
"omit": ์ธ์ฆ ์ ๋ณด ํฌํจ ์ ํจ (๊ธฐ๋ณธ๊ฐ)"same-origin": ๋์ผ ์ถ์ฒ ์์ฒญ์๋ง ์ธ์ฆ ์ ๋ณด ํฌํจ"include": ํญ์ ์ธ์ฆ ์ ๋ณด ํฌํจ
XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.withCredentials = true; // ์ธ์ฆ ์ ๋ณด ํฌํจ
xhr.open("GET", "https://api.example.com/user-data");
xhr.send();์๋ฒ ์ค์
์ธ์ฆ ์ ๋ณด๋ฅผ ํฌํจํ ์์ฒญ์ ๋ํด ์๋ฒ๋ ๋ค์์ ๋ฐ๋์ ์ค์ ํด์ผ ํฉ๋๋ค:
Access-Control-Allow-Origin: https://app.example.com # ์ ํํ ์ถ์ฒ ๋ช
์ (* ๋ถ๊ฐ)
Access-Control-Allow-Credentials: true์ธ์ฆ ์ ๋ณด ํฌํจ ์์ฒญ ์ ์ฒด ํ๋ฆ
// ํด๋ผ์ด์ธํธ
fetch("https://api.example.com/protected-data", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: 123 })
});// Preflight ์์ฒญ
OPTIONS /protected-data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
// Preflight ์๋ต
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com # * ๋ถ๊ฐ
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true # ํ์
// ์ค์ ์์ฒญ
POST /protected-data HTTP/1.1
Origin: https://app.example.com
Cookie: sessionId=abc123 # ์ฟ ํค ํฌํจ
Content-Type: application/json
{"id": 123}
// ์ค์ ์๋ต
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com # * ๋ถ๊ฐ
Access-Control-Allow-Credentials: true # ํ์
Set-Cookie: sessionId=xyz789
{"status": "success"}์๋ฒ ์ธก CORS ๊ตฌํ ์์
Node.js/Express
const express = require('express');
const app = express();
// ๋ชจ๋ ์์ฒญ์ CORS ํ์ฉ (๊ณต๊ฐ API)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
// ํน์ ์ถ์ฒ๋ง ํ์ฉํ๊ณ ์ธ์ฆ ์ ๋ณด ํฌํจ
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Preflight ์์ฒญ ์ฒ๋ฆฌ
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
app.get('/api/data', (req, res) => {
res.json({ message: 'Hello CORS' });
});
app.listen(3000);Spring Boot (Java)
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Content-Type", "Authorization")
.allowCredentials(true)
.maxAge(3600);
}
};
}
}CORS ์๋ฌ ํด๊ฒฐ
| ์๋ฌ ๋ฉ์์ง | ์์ธ | ํด๊ฒฐ ๋ฐฉ๋ฒ |
|---|---|---|
| Access-Control-Allow-Origin header missing | ์๋ฒ๊ฐ CORS ํค๋๋ฅผ ๋ณด๋ด์ง ์์ | ์๋ฒ์์ Access-Control-Allow-Origin ํค๋ ์ถ๊ฐ |
| Access-Control-Allow-Origin does not match | ์ถ์ฒ๊ฐ ํ์ฉ ๋ชฉ๋ก์ ์์ | ์๋ฒ์์ ํด๋น ์ถ์ฒ๋ฅผ ํ์ฉ ๋ชฉ๋ก์ ์ถ๊ฐ |
| Credentials mode is โincludeโ but Access-Control-Allow-Credentials is missing | ์ธ์ฆ ์ ๋ณด ํฌํจ ์์ฒญ์ credentials ํค๋ ์์ | ์๋ฒ์์ Access-Control-Allow-Credentials: true ์ถ๊ฐ |
| Method not found in Access-Control-Allow-Methods | HTTP ๋ฉ์๋๊ฐ ํ์ฉ๋์ง ์์ | ์๋ฒ์์ ํด๋น ๋ฉ์๋๋ฅผ Access-Control-Allow-Methods์ ์ถ๊ฐ |
| Header not found in Access-Control-Allow-Headers | ์ปค์คํ ํค๋๊ฐ ํ์ฉ๋์ง ์์ | ์๋ฒ์์ ํด๋น ํค๋๋ฅผ Access-Control-Allow-Headers์ ์ถ๊ฐ |
CORS๊ฐ ๋ณดํธํ๋ ๊ฒ vs ๋ณดํธํ์ง ์๋ ๊ฒ
โ CORS๊ฐ ๋ณดํธํ๋ ๊ฒ
- ๋ฌด๋จ ๋ฐ์ดํฐ ์ ๊ทผ ๋ฐฉ์ง: ์ ์์ ์ธ ์น์ฌ์ดํธ๊ฐ ์ฌ์ฉ์์ ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ๋ค๋ฅธ ์ฌ์ดํธ์ ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ์ฝ๋ ๊ฒ์ ์ฐจ๋จ
- ๋ช ์์ ๊ถํ ๋ถ์ฌ: ์๋ฒ๊ฐ ํ์ฉํ ์ถ์ฒ๋ง ๋ฆฌ์์ค์ ์ ๊ทผ ๊ฐ๋ฅ
- ๋ฏผ๊ฐํ ์์ ๋ณดํธ: GET ์ธ์ ์์ฒญ์ preflight๋ฅผ ํตํด ์๋ฒ๊ฐ ๋ช ์์ ์ผ๋ก ํ์ฉํด์ผ ํจ
โ CORS๊ฐ ๋ณดํธํ์ง ์๋ ๊ฒ
- CSRF(Cross-Site Request Forgery) ๊ณต๊ฒฉ: CORS๋ CSRF๋ฅผ ๋ฐฉ์งํ์ง ์์ต๋๋ค. ๋ณ๋์ CSRF ํ ํฐ์ด ํ์ํฉ๋๋ค
- ์ค๊ฐ์ ๊ณต๊ฒฉ(Man-in-the-Middle): CORS๋ ์ ์ก ๊ณ์ธต ๋ณด์๊ณผ ๋ฌด๊ดํฉ๋๋ค. HTTPS/TLS๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค
- ์๋ฒ ์ธก ์ทจ์ฝ์ : CORS๋ ํด๋ผ์ด์ธํธ ์ธก ๋ณด์๋ง ๋ค๋ฃน๋๋ค. ์๋ฒ ์ธก ๋ณด์์ ๋ณ๋๋ก ๊ตฌํํด์ผ ํฉ๋๋ค
๋ณด์ Best Practices
# โ ๋์ ์: ์ธ์ฆ ์ ๋ณด์ ์์ผ๋์นด๋ ํจ๊ป ์ฌ์ฉ ๋ถ๊ฐ
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
# โ
์ข์ ์: ํน์ ์ถ์ฒ๋ง ํ์ฉ
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
# โ
์ข์ ์: Preflight ์บ์ฑ ์ ์ ํ ์ค์
Access-Control-Max-Age: 3600 # 1์๊ฐ
# โ
์ข์ ์: ํ์ํ ๋ฉ์๋๋ง ํ์ฉ
Access-Control-Allow-Methods: GET, POST # PUT, DELETE๋ ํ์ํ ๋๋ง