๊ฐœ๋ฐœ ์ง€์‹/WEB ์ง€์‹

๐Ÿช CORS ์ฟ ํ‚ค ์ „์†กํ•˜๊ธฐ (withCredentials ์˜ต์…˜)

์ธํŒŒ_ 2022. 7. 18. 13:50

cors-withCredentials

๐Ÿคฌ 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 ์—๋Ÿฌ๊ฐ€ ๋ฐ˜๊ฒจ์ฃผ์—ˆ๋‹ค. 

cors-withCredentials

๋˜ํ•œ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์—ด์–ด๋ณด๋ฉด ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ƒ์„ฑ๋œ ์„ธ์…˜ 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๋กœ ์„ค์ •ํ•ด์ค˜์•ผ ํ•œ๋‹ค๋Š” ๋ง์ด๋‹ค.

  1. ํ‘œ์ค€ CORS์š”์ฒญ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ๋ณด๋‚ผ ์ˆ˜ ์—†๋‹ค.
  2. ํ”„๋ก ํŠธ์—์„œ ajax ์š”์ฒญํ•  ๋•Œ, withCredentials๋ถ€๋ถ„์„ true๋กœ ํ•ด์„œ ์ˆ˜๋™์œผ๋กœ CORS ์š”์ฒญ์— ์ฟ ํ‚ค๊ฐ’์„ ๋„ฃ์–ด์ค˜์•ผ ํ•œ๋‹ค.
  3. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์„œ๋ฒ„๋„ ์‘๋‹ตํ—ค๋”์— Access-Control-Allow-Credentials๋ฅผ true๋กœ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.

cors-withCredentials


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 ์š”์ฒญํ•˜๊ฒŒ ๋˜๋ฉด ๋ชจ๋‘ ๊ฑฐ๋ถ€๊ฐ€ ๋œ๋‹ค.

withCredentials

์ด๋•Œ ์„œ๋ฒ„์—์„œ ์‘๋‹ต ํ—ค๋”๋ฅผ ์„ค์ •ํ•˜๋Š”๋ฐ ์žˆ์–ด ๋ช‡๊ฐ€์ง€ ์ œ์•ฝ์ด ์žˆ์–ด ์ฃผ์˜ํ•˜์—ฌ์•ผ ํ•œ๋‹ค.

  1. ์‘๋‹ต ํ—ค๋”์˜ Access-Control-Allow-Credentials ํ•ญ๋ชฉ์„ true๋กœ ์„ค์ •
  2. ์‘๋‹ต ํ—ค๋”์˜ Access-Control-Allow-Origin์˜ ๊ฐ’์— ์™€์ผ๋“œ์นด๋“œ ๋ฌธ์ž("*")๋Š” ๋ณด์•ˆ์ƒ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.
  3. ์‘๋‹ต ํ—ค๋”์˜ Access-Control-Allow-Methods์˜ ๊ฐ’์— ์™€์ผ๋“œ์นด๋“œ ๋ฌธ์ž("*")๋Š” ๋ณด์•ˆ์ƒ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.
  4. ์‘๋‹ต ํ—ค๋”์˜ Access-Control-Allow-Headers์˜ ๊ฐ’์— ์™€์ผ๋“œ์นด๋“œ ๋ฌธ์ž("*")๋Š” ๋ณด์•ˆ์ƒ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

๋งŒ์ผ ์ด๋ฅผ ์–ด๊ธธ๊ฒฝ์šฐ ์•„๋ž˜์™€ ๊ฐ™์€ ๋˜๋‹ค๋ฅธ ์ข…๋ฅ˜์˜ CORS ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ์ ‘ํ•˜๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

cors-withCredentials
Access-Control-Allow-Credentials ์„ค์ • ์•ˆํ–ˆ์„ ๊ฒฝ์šฐ
cors-withCredentials
Access-Control-Allow-Origin๊ฐ€ * ๋กœ ์„ค์ •๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ

์ฐธ๊ณ ๋กœ ์˜ˆ๋น„ ์š”์ฒญ(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