โ ์ฌํ์ด๋ค ์ฌํ์ด๋ค ํ๋ฉด์ ํ๋ก์ ํธ ์ธ๊ฐ ํ๋ค. โ
- ๊ฐ๋ฐ์ ์๋ด(Programmerโs Proverbs)

๐คฌ CORS๋ฅผ ํ์ฉํ๋๋ฐ๋ ์ฟ ํค๊ฐ ๋์ด๊ฐ์ง ์๋ ํ์
๋ณดํต ์น์ ๊ตฌ์ฑํ ๋ ๋ฆฌ์กํธ(React)๋ ๋ทฐ(Vue)์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ / ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ฐ๋ก ํ๋ก ํธ ์๋ฒ๋ฅผ ์คํํ์ฌ ๊ฐ๋ฐํ๊ฒ ๋๋ค. ๋ง์ผ ํด๋ผ์ด์ธํธ ์๋ฒ๊ฐ http://localhost:3000 ์ด๊ณ API ์๋ฒ๊ฐ http://localhost:8080 ์ด๋ผ๊ณ ๊ฐ์ ํ์. ์๋ก ๊ฐ์ Host์ด๊ณ Port๋ง ๋ค๋ฅธ ์ ์ด๋ค. ๋ก๊ทธ์ธ ํ๋ฉด์ ๊ตฌ์ฑ์ ์๋ฃํ๊ณ ํ ์คํธ๋ฅผ ์ํด axios๋ก ๋ก๊ทธ์ธ ์์ฒญ์ ์๋ฒ์ ๋ณด๋๋ค.
axios.post('http://localhost:8080/login', {
profile: { username: username, password: password }
})
ํ์ง๋ง ๊ฒฐ๊ณผ๋ ๋ก๊ทธ์ธ ์ฑ๊ณต์ด ์๋ ๋ค์๊ณผ ๊ฐ์ ์๋ป๊ฑด CORS ์๋ฌ๊ฐ ๋ฐ๊ฒจ์ฃผ์๋ค.

๋ํ ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ด์ด๋ณด๋ฉด ์๋ฒ๋ก๋ถํฐ ์์ฑ๋ ์ธ์
ID๊ฐ ๋ค์ ์ฟ ํค๊ฐ๋ ์๋ค. ๋ถ๋ช
์๋ฒ์์ ๋ฐ๋ก Access-Control-Allow-origin ํค๋ ๊ฐ์ ๋ชจ๋ ์ถ์ฒ๋ฅผ ํ์ฉ์ผ๋ก ์ค์ ํ์์๋ ๋ถ๊ตฌํ๊ณ ์ ์ด๋ฐ ์๋ฌ๊ฐ ๋จ๋ ๊ฒ์ผ๊น?
[WEB] ๐ ์ ๋ช ๋์ CORS ๊ฐ๋ & ํด๊ฒฐ๋ฒ - ์ ๋ฆฌ ๋ํ์ ๐
์ ๋ช ๋์ CORS ์๋ฌ ๋ฉ์ธ์ง ์น ๊ฐ๋ฐ์ ํ๋ค๋ณด๋ฉด ๋ฐ๋์ ๋ง์ฃผ์น๋ ๋ฉ๋ฉ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ๋ก CORS ์ด๋ค. ์น ๊ฐ๋ฐ์ ์ ์ ์ ๊ณ ์์ด๋ผ๊ณ ํ ์ ๋๋ก, CORS๋ ๋๊ตฌ๋ ํ ๋ฒ ์ ๋๋ ๊ฒช๊ฒ ๋๋ค๊ณ ํด๋ ๊ณผ์ธ์ด
inpa.tistory.com
๐ช withCredentials ์ต์ ์ผ๋ก ์ฟ ํค ๋ณด๋ด๊ธฐ
Credentials ์ด๋ ์ฟ ํค, Authorization ์ธ์ฆ ํค๋, TLS client certificates(์ฆ๋ช ์)๋ฅผ ๋ดํฌํ๋ ์๊ฒฉ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋งํ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ๋ธ๋ผ์ฐ์ ๊ฐ ์ ๊ณตํ๋ ์์ฒญ API ๋ค์ ๋ณ๋์ ์ต์ ์์ด ๋ธ๋ผ์ฐ์ ์ ์ฟ ํค์ ๊ฐ์ ์ธ์ฆ๊ณผ ๊ด๋ จ๋ ๋ฐ์ดํฐ๋ฅผ ํจ๋ถ๋ก ์์ฒญ ๋ฐ์ดํฐ์ ๋ด์ง ์๋๋ก ๋์ด์๋ค. ์ด๋ ์๋ต์ ๋ฐ์๋๋ ๋ง์ฐฌ๊ฐ์ง์ด๋ค. ๋ฐ๋ผ์ ์์ฒญ๊ณผ ์๋ต์ ์ฟ ํค๋ฅผ ํ์ฉํ๊ณ ์ถ์ ๊ฒฝ์ฐ, ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ์ต์ ์ด ๋ฐ๋ก withCredentials ์ต์ ์ด๋ค.
withCredentials ์ต์
์ ๋จ์ด ๊ทธ๋๋ก, ๋ค๋ฅธ ๋๋ฉ์ธ(Cross Origin)์ ์์ฒญ์ ๋ณด๋ผ ๋ ์์ฒญ์ ์ธ์ฆ(credential) ์ ๋ณด๋ฅผ ๋ด์์ ๋ณด๋ผ ์ง๋ฅผ ๊ฒฐ์ ํ๋ ํญ๋ชฉ์ด๋ค. ์ฆ, ์ฟ ํค๋ ์ธ์ฆ ํค๋ ์ ๋ณด๋ฅผ ํฌํจ์์ผ ์์ฒญํ๊ณ ์ถ๋ค๋ฉด, ํด๋ผ์ด์ธํธ์์ API ์์ฒญ ๋ฉ์๋๋ฅผ ๋ณด๋ผ๋ withCredentials ์ต์
์ true๋ก ์ค์ ํด์ผํ๋ค. ๋ํ ์ธ์ฆ๋ ์์ฒญ์ ์ ์์ ์ผ๋ก ์ํํ๊ธฐ ์ํด์ ํด๋ผ์ด์ธํธ ๋ฟ๋ง ์๋๋ผ ์๋ฒ์์๋ Access-Control-Allow-Credentials ํค๋๋ฅผ true๋ก ํจ์ผ๋ก์จ ์ธ์ฆ ์ต์
์ ์ค์ ํด์ฃผ์ด์ผ ํ๋ค.
์ ๋ฆฌํ์๋ฉด ํด๋ผ์ด์ธํธ๋ ์๋ฒ๋ ๋๋ค Credentials ๋ถ๋ถ์ true๋ก ์ค์ ํด์ค์ผ ํ๋ค๋ ๋ง์ด๋ค.
- ํ์ค CORS์์ฒญ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฟ ํค๋ฅผ ์ค์ ํ๊ฑฐ๋ ๋ณด๋ผ ์ ์๋ค.
- ํ๋ก ํธ์์ ajax ์์ฒญํ ๋,
withCredentials๋ถ๋ถ์ true๋ก ํด์ ์๋์ผ๋ก CORS ์์ฒญ์ ์ฟ ํค๊ฐ์ ๋ฃ์ด์ค์ผ ํ๋ค. - ๋ง์ฐฌ๊ฐ์ง๋ก ์๋ฒ๋ ์๋ตํค๋์
Access-Control-Allow-Credentials๋ฅผ true๋ก ์ค์ ํด์ผ ํ๋ค.

1. ํด๋ผ์ด์ธํธ ์ฒ๋ฆฌ ๋ถ๋ถ
์ด๋ค ๋ฉ์๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋๋์ ๋ฐ๋ผ withCredentials ์ต์
์ ํ์ฑํํ๋ ๋ฌธ๋ฒ์ด ๋ค๋ฅด๋ค. ๋ฐ๋ผ์ ์ด๋ฒ ํฌ์คํ
์์๋ ์ฌ๋ฌ ajax์์ฒญ ๋ฐฉ๋ฒ์ ๋ชจ๋ ์๊ฐํด ๋ณธ๋ค.
axios ์ค์
withCredentials ์ต์
๋ถ๋ถ์ axios ์ ์ญ ์ค์ ์ผ๋ก ์ฒ๋ฆฌํ๊ฑฐ๋, axios ์์ฒญ ๋ฉ์๋์ ์ต์
์ธ์๋ก ๋ฃ์ด ๋ณด๋ผ์ ์๋ค. (๋์ค ํ)
// 1. axios ์ ์ญ ์ค์
axios.defaults.withCredentials = true; // withCredentials ์ ์ญ ์ค์
// 2. axios ์ต์
๊ฐ์ฒด๋ก ๋ฃ๊ธฐ
axios.post(
'https://example.com:1234/users/login',
{ profile: { username: username, password: password } },
{ withCredentials: true }
).then(response => {
console.log(response);
console.log(response.data);
})
fetch ์ค์
๋ง์ผ axios๊ฐ ์๋ fetch ๋ฉ์๋๋ก api ์์ฒญ์ ํ๊ณ ์ถ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํ๋ค. (๋ฌธ๋ฒ์ด ์ฝ๊ฐ ๋ค๋ฅด๋ค)
fetch("https://example.com:1234/users/login", {
method: "POST",
credentials: "include", // ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ํต์ ํ ๋ ์ฟ ํค ๊ฐ์ ๊ณต์ ํ๊ฒ ๋ค๋ ์ค์
})
jQuery ์ค์
$.ajax({
url: "https://example.com:1234/users/login",
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
xhrFields: {
withCredentials: true // ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ํต์ ํ ๋ ์ฟ ํค ๊ฐ์ ๊ณต์ ํ๊ฒ ๋ค๋ ์ค์
},
success: function (retval, textStatus) {
console.log( JSON.stringify(retval));
}
error: function () {
console.log("error");
}
});
XMLHttpRequest ๊ฐ์ฒด
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);
2. ์๋ฒ ์ฒ๋ฆฌ ๋ถ๋ถ
credential ์ ๋ณด๊ฐ ํฌํจ๋์ด ์๋ ์์ฒญ์ด ์ ์์ ์ผ๋ก ์ฒ๋ฆฌ๋๊ธฐ ์ํด์๋, ํด๋น ์์ฒญ์ ๋ฐ๋ ์๋ฒ ์ธก์์๋ ๋ค์๊ณผ ๊ฐ์ ์ค์ ์ด ํ์ํ๋ค. ๋ง์ผ ์๋ฒ์์ ๋ณ๋์ ์ฒ๋ฆฌ ์์ด ํด๋ผ์ด์ธํธ ๋ถ๋ถ์ withCredentials ์ต์
๋ง ํ์ฑํ ํ์ฑ ์๋ฒ์ cors ์์ฒญํ๊ฒ ๋๋ฉด ๋ชจ๋ ๊ฑฐ๋ถ๊ฐ ๋๋ค.

์ด๋ ์๋ฒ์์ ์๋ต ํค๋๋ฅผ ์ค์ ํ๋๋ฐ ์์ด ๋ช๊ฐ์ง ์ ์ฝ์ด ์์ด ์ฃผ์ํ์ฌ์ผ ํ๋ค.
- ์๋ต ํค๋์
Access-Control-Allow-Credentialsํญ๋ชฉ์ true๋ก ์ค์ - ์๋ต ํค๋์
Access-Control-Allow-Origin์ ๊ฐ์ ์์ผ๋์นด๋ ๋ฌธ์("*")๋ ๋ณด์์ ์ฌ์ฉํ ์ ์๋ค. - ์๋ต ํค๋์
Access-Control-Allow-Methods์ ๊ฐ์ ์์ผ๋์นด๋ ๋ฌธ์("*")๋ ๋ณด์์ ์ฌ์ฉํ ์ ์๋ค. - ์๋ต ํค๋์
Access-Control-Allow-Headers์ ๊ฐ์ ์์ผ๋์นด๋ ๋ฌธ์("*")๋ ๋ณด์์ ์ฌ์ฉํ ์ ์๋ค.
๋ง์ผ ์ด๋ฅผ ์ด๊ธธ๊ฒฝ์ฐ ์๋์ ๊ฐ์ ๋๋ค๋ฅธ ์ข ๋ฅ์ CORS ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ์ ํ๊ฒ ๋ ๊ฒ์ด๋ค.


์ฐธ๊ณ ๋ก ์๋น ์์ฒญ(Preflight)๊ฐ ํ์ ์๋ ๋จ์ ์์ฒญ์ ๊ฒฝ์ฐ(GET ์์ฒญ), Access-Control-Allow-Methods์ Access-Control-Allow-Headers ํค๋๋ ์์ด๋ ๋๋ค.
Node ์๋ฒ ์ค์
์๋ฒ์ response ํค๋(Header) ๊ฐ์ผ๋ก Access-Control ์ค์ ์ ํด์ค๋ค.
var http = require('http');
const PORT = process.env.PORT || 3000;
var httpServer = http.createServer(function (request, response) {
// Setting up Headers
response.setHeader('Access-Control-Allow-origin', '*'); // ๋ชจ๋ ์ถ์ฒ(orogin)์ ํ์ฉ
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); // ๋ชจ๋ HTTP ๋ฉ์๋ ํ์ฉ
response.setHeader('Access-Control-Allow-Credentials', 'true'); // ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ์ ์ฟ ํค ์ฃผ๊ณ ๋ฐ๊ธฐ ํ์ฉ
// ...
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('ok');
});
httpServer.listen(PORT, () => {
console.log('Server is running at port 3000...');
});
Express ์๋ฒ ์ค์
> npm install cors
const express = require('express')
const cors = require('cors');
const app = express();
app.use(cors({
origin: '*', // ์ถ์ฒ ํ์ฉ ์ต์
credential: 'true' // ์ฌ์ฉ์ ์ธ์ฆ์ด ํ์ํ ๋ฆฌ์์ค(์ฟ ํค ..๋ฑ) ์ ๊ทผ
}));
...๋ผ์ฐํฐ
[NODE] ๐ CORS ์ค์ ํ๊ธฐ (cors ๋ชจ๋)
CORS ๋? [WEB] ๐ CORS ๐ฏ ์ ๋ฆฌ & ํด๊ฒฐ ๋ฐฉ๋ฒ ๐ CORS(Cross Origin Resource Sharing) CORS ์ ์ฑ ์ ์ฐ๋ฆฌ๊ฐ ๊ฐ์ ธ์ค๋ ๋ฆฌ์์ค๋ค์ด ์์ ํ์ง ๊ฒ์ฌํ๋ ๊ด๋ฌธ์ด๋ค. ์น๊ฐ๋ฐ์ ํ๋ ์ฌ๋๋ค์ ์ด CORS ์ ์ฑ ์๋ฐ์ผ๋ก ์ธํด
inpa.tistory.com
Spring ์๋ฒ ์ค์
// ์คํ๋ง ์๋ฒ ์ ์ญ์ ์ผ๋ก CORS ์ค์
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080", "http://localhost:8081") // ํ์ฉํ ์ถ์ฒ
.allowedMethods("GET", "POST") // ํ์ฉํ HTTP method
.allowCredentials(true) // ์ฟ ํค ์ธ์ฆ ์์ฒญ ํ์ฉ
.maxAge(3000) // ์ํ๋ ์๊ฐ๋งํผ pre-flight ๋ฆฌํ์คํธ๋ฅผ ์บ์ฑ
}
}
# ์ฐธ๊ณ ์๋ฃ
https://portswigger.net/web-security/cors
https://livebook.manning.com/book/cors-in-action/chapter-5/81
์ด ๊ธ์ด ์ข์ผ์ จ๋ค๋ฉด ๊ตฌ๋ & ์ข์์
์ฌ๋ฌ๋ถ์ ๊ตฌ๋
๊ณผ ์ข์์๋
์ ์์๊ฒ ํฐ ํ์ด ๋ฉ๋๋ค.