졸업 프로젝트를 진행하면서도 만난 그 녀석... CORS 에러! 조원들과 학교 1층 카페에 모여서 열심히 구글링 하면서 문제를 해결했던 기억이 있다.
그때는 당최 CORS 에러가 뭔지 정확하고 자세하게는 모르겠지만, 여하튼 프론트엔드 개발자들이 넘어야 할 산 중에 하나라고 설명하는 사람들이 많아서 나중에 또 만나겠구나... 이 친구... 싶긴 했다.
그런데 아니나 다를까 부트 캠프에서 딱 마주쳤다! 그치만 이번엔 에러로 만난 것이 아니라 수업 내용으로 만났다. 이때까지 이 친구를 CORS 에러 라고 부른 것이 미안했다... CORS는 저 에러의 해결 방안이었던 것이다! 이걸 먼저 알았더라면 졸프가 더 쉬웠을 텐데... 싶었다😂 그래도 이제 어디에서 만나도 두렵지 않아!
동일 출처 정책: ‘같은 출처의 리소스만 공유가 가능하다’라는 정책
잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄여준다.
브라우저에서 말하는 '출처(origin)'라는 건 뭘까?
출처는 프로토콜, 호스트, 포트의 조합으로 되어있다.
이 중 하나라도 다르면 동일한 출처로 보지 않는다.
MDN에서는 CORS를 다음과 같이 정의하고 있다.
교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.
즉, 브라우저는 SOP에 의해 기본적으로 다른 출처의 리소스 공유를 막지만, CORS를 사용하면 접근 권한을 얻을 수 있게 되는 것!!!
이걸 알고 아래 에러 메세지를 다시 보면
"다른 출처의 리소스를 가져오려고 했지만 SOP 때문에 접근이 불가능합니다.
CORS 설정을 통해 서버의 응답 헤더에 ‘Access-Control-Allow-Origin’을 작성하면 접근 권한을 얻을 수 있습니다."
라는 뜻이라는 걸 알 수 있다!
즉, 이 에러는 CORS 때문이 아니라, SOP 때문이었던 것. CORS는 오히려 해결의 열쇠였던 것이다!
실제 요청을 보내기 전, OPTIONS 메서드로 사전 요청을 보내 해당 출처 리소스에 접근 권한이 있는지부터 확인하는 것
브라우저는 서버에 실제 요청을 보내기 전에 프리플라이트 요청을 보내고, 응답 헤더의 Access-Control-Allow-Origin
으로 요청을 보낸 출처가 돌아오면 실제 요청을 보내게 된다.
만약에 요청을 보낸 출처가 접근 권한이 없다면 브라우저에서 CORS 에러를 띄우게 되고, 실제 요청은 전달되지 않는다.
실제 요청을 보내기 전에 미리 권한 확인을 할 수 있기 때문에, 실제 요청을 처음부터 통째로 보내는 것보다 리소스 측면에서 효율적이다.
CORS에 대비가 되어있지 않은 서버를 보호할 수 있다.
특정 조건이 만족되면 프리플라이트 요청을 생략하고 요청을 보내는 것
Accept
, Accept-Language
, Content-Language
, Content-Type
헤더의 값만 수동으로 설정할 수 있다.Content-Type
헤더에는 application/x-www-form-urlencoded
, multipart/form-data
, text/plain
값만 허용된다. 요청 헤더에 인증 정보를 담아 보내는 요청
출처가 다를 경우에는 별도의 설정을 하지 않으면 쿠키를 보낼 수 없습니다. 민감한 정보이기 때문
이 경우에는 클라이언트, 서버 양측 모두 CORS 설정이 필요
withCredentials : true
Access-Control-Allow-Credentials : true
서버 측에서 Access-Control-Allow-Origin
을 설정할 때, 모든 출처를 허용한다는 뜻의 와일드카드(*)로 설정하면 에러가 발생. 인증 정보를 다루는 만큼 출처를 정확하게 설정해주어야 한다.
헤더의 값을 설정하는 방법만 알면 다양한 환경에서 설정 가능
const http = require('http');
const server = http.createServer((request, response) => {
// 모든 도메인
response.setHeader("Access-Control-Allow-Origin", "*");
// 특정 도메인
response.setHeader("Access-Control-Allow-Origin", "https://codestates.com");
// 인증 정보를 포함한 요청을 받을 경우
response.setHeader("Access-Control-Allow-Credentials", "true");
})
const cors = require("cors");
const app = express();
//모든 도메인
app.use(cors());
//특정 도메인
const options = {
origin: "https://codestates.com", // 접근 권한을 부여하는 도메인
credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
optionsSuccessStatus: 200, // 응답 상태 200으로 설정
};
app.use(cors(options));
//특정 요청
app.get("/example/:id", cors(), function (req, res, next) {
res.json({ msg: "example" });
});
🔽 응답으로 'Hello World!'를 보내는 Express 서버 코드 🔽
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
메서드와 url(/lower, /upper 등)로 분기점을 만드는 것을 라우팅(Routing)이라고 한다.
🔽 Express로 라우팅을 구현한 코드 🔽
const router = express.Router()
router.get('/lower', (req, res) => {
res.send(data);
})
router.post('/lower', (req, res) => {
// do something
})
미들웨어(Middleware)는 자동차 공장의 공정과 비슷하다.
컨베이어 벨트 위에 올라가 있는 요청(Request)에 필요한 기능을 더하거나,
문제가 발견된 불량품을 밖으로 걷어내는 역할을 한다.
미들웨어는 express의 가장 큰 장점
Node.js로 HTTP body(payload)를 받을 때는
네트워크상의 chunk를 합치고, buffer를 문자열로 변환하는 작업이 필요
➡ body-parser 미들웨어를 사용하면 아래처럼 간단하게 처리할 수 있다.
// body-parser 사용
const bodyParser = require('body-parser');
const jsonParser = bodyParser.json();
// 생략
app.post('/users', jsonParser, function (req, res) {
})
/* Express v4.16.0부터는 body-parser를 따로 설치하지 않고,
Express 내장 미들웨어인 express.json()을 사용 */
const jsonParser = express.json();
// 생략
app.post('/api/users', jsonParser, function (req, res) {
})
Node.js에서는 이 메서드 등을 이용하여 라우팅마다 헤더를 매번 넣어주어야 할 뿐만 아니라, OPTIONS 메서드에 대한 라우팅도 따로 구현해야 한다.
➡ cors 미들웨어를 사용하면 이러한 과정을 간단하게 처리할 수 있다.
🔽모든 요청에 대해 cors를 허용할 때🔽
const cors = require('cors');
// 생략
app.use(cors());
🔽특정 요청에 대해서만 cors를 허용할 때🔽
const cors = require('cors')
// 생략
app.get('/products/:id', cors(), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for a Single Route'})
})