이번 포스팅에서는 CORS에 대해서 알아보고자 한다.
< 목차 >
- CORS ??
- 서버 나누기 (프론트서버 & 백서버)
- app.use( cors( ) )
1. CORS ??
CORS란 "Cross-Origin Resource Sharing"의 약자로 "교차 출처 리소스 공유"라는 의미를 갖고 있다. 위키백과를 살펴보면 다음과 같은 설명이 나온다.
교차 출처 리소스 공유(CORS)는 웹 페이지 상의 제한된 리소스를 최초 자원이 서비스된 도메인 밖의 다른 도메인으로부터 요청할 수 있게 허용하는 구조이다. (출처 - 위키백과)
브라우저에서는 보안상의 이슈로 교차 출처(cross-origin)의 HTTP 요청들을 제한하게 된다. 쉽게 얘기해서 브라우저를 열고 네이버에 접속한 상태에서 브라우저의 javascript 코드를 이용해 구글 서버로 요청을 보낼 수 없다는 의미이다. 이러한 cross-origin 요청을 하기 위해서는 서버측에서의 동의를 필요로 한다. HTTP-header를 이용해서 서버쪽의 허락을 구하게 되는데 만약 서버쪽에서 동의한다면 브라우저에서는 요청을 허락하게 되고 동의하지 않는다면 브라우저에서 해당 요청들을 거절하게 된다.
클라이언트에서 무분별하게 다른 리소스에 접근하는 것을 막기 위해 등장한 것이 CORS라는 개념이다. 보안상의 이슈로 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한한다. 웹 애플리케이션은 자신의 출처와 동일한 리소스만을 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 한다.
다음은 MDN에 나와있는 CORS에 대한 설명이니 참고하도록 하자.
교차 출처 리소스 공유(Cross-Origin Resource Sharing)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다. 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행한다.
교차 출처 요청의 예시 : "https://도메인A.com"의 프론트엔드 JavaScript 코드가 XMLHttpRequest를 사용해서 "https://도메인B.com/data.json"을 요청하는 경우.
2. 서버 나누기 (프론트서버 & 백서버)
CORS의 개념에 대해 좀 더 이해해보기 위해 프론트서버와 백서버로 서버를 나누는 작업을 진행해보고자 한다. 프론트서버에서는 화면을 랜더하는 작업들만을 수행할 것이고 브라우저에서 서버쪽으로 데이터를 요청하는 작업들은 백서버에서 처리할 것이다. 아래의 그림을 보면서 전체적인 구조를 파악해보자.
디렉토리는 다음과 같이 front 디렉토리와 back 디렉토리로 나누었다. front 디렉토리에서는 프론트 서버를 구동시키고 있고 back 디렉토리에서는 백 서버를 구동시키고 있다.
프론트 서버의 url로 요청을 보냈을 때는 아래와 같이 브라우저에서 해당 내용을 잘 가져오는 것을 확인할 수 있다.
하지만 프론트 서버의 포트번호가 아닌 백서버의 포트번호로 요청을 보낼 경우 에러가 발생하게 된다.
CORS policy에 의해 localhost:3001에서 보낸 요청이 차단되는 것을 알 수 있다. 이러한 문제를 해결하기 위해서는 백엔드 서버에서 해당 요청을 허락하는 작업을 진행해줘야 한다. 참고로 백엔드 서버에서는 응답헤더를 통해 관련 내용들을 전달하게 된다.
// 백엔드 server.js
const express = require('express')
const app = express()
app.use(express.json())
app.use(express.urlencoded({extended: true}))
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3001')
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE') // methods 사용여부
next()
})
app.get('/', (req, res) => {
res.send('data')
})
app.listen(4001, ()=>{
console.log('back server onload')
})
app.use( ) 라우터를 사용해서 res.setHeader( )를 통해 요청을 허락한다는 내용을 전달해주면 이제 브라우저에서 백엔드 서버에 요청을 보내 데이터를 전달 받을 수 있게 된다.
요청뿐만 아니라 쿠키 생성에 있어서도 백엔드에서 허락을 해줘야만 하는데 응답 헤더에 다음과 같은 내용을 추가해 주면 된다.
res.setHeader('Access-Control-Allow-Credentials','true')
그리고 요청을 보내는 스크립트에서도 옵션값으로 withCredentials: true 값을 전달해줘야만 한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="https://unpkg.com/axios@0.26.0/dist/axios.min.js"></script>
</head>
<body>
<button id="btn">버튼</button>
<script type="text/javascript">
const btn = document.querySelector('#btn')
btn.addEventListener('click', async ()=>{
alert('버튼클릭')
const opt = {
method: 'get',
withCredentials: true
}
const response = await axios.get('http://localhost:4001/', opt)
console.log(response.data)
})
</script>
</body>
</html>
뿐만 아니라 브라우저에서 백엔드 서버로 스크립트를 통해 JSON 타입의 데이터를 전달하는 경우 옵션값에서 "Content-type": "application/json"을 설정하게 되는데 이때도 마찬가지로 백서버 쪽에서 아래와 같은 처리를 해줘야 한다.
res.setHeader('Access-Control-Allow-Headers','Content-type')
아래는 백엔드 서버의 server.js 파일이다. 기존의 app.get( ) 라우터를 app.post( )로 변경하였고 console.log( req.body ) 를 통해 데이터가 제대로 전달되었는지 확인할 수 있도록 하였다.
const express = require('express')
const app = express()
app.use(express.json())
app.use(express.urlencoded({extended: true}))
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3001')
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE') // methods 사용여부
res.setHeader('Access-Control-Allow-Credentials','true')
res.setHeader('Access-Control-Allow-Headers','Content-type')
next()
})
app.post('/', (req, res) => {
console.log(req.body)
res.setHeader('Set-Cookie', 'name=bitkunst;')
res.send('data')
})
app.listen(4001, ()=>{
console.log('back server onload')
})
브라우저의 html 코드는 아래와 같다. 백엔드 서버에 JSON 형태의 데이터를 전달하고 있기 때문에 opt의 method 속성값을 post로 변경하였고 "Content-type": "application/json" 속성을 추가하였다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="https://unpkg.com/axios@0.26.0/dist/axios.min.js"></script>
</head>
<body>
<button id="btn">버튼</button>
<script type="text/javascript">
const btn = document.querySelector('#btn')
btn.addEventListener('click', async ()=>{
alert('버튼클릭')
const body = {
userid: 'bitkunst',
username: '비트쿤스트'
}
const opt = {
method: 'post',
withCredentials: true,
"Content-type": "application/json"
}
const response = await axios.post('http://localhost:4001/', body, opt)
console.log(response.data)
})
</script>
</body>
</html>
3. app.use( cors( ) )
지금까지 우리가 작성했던 코드들은 백엔드 서버에서 CORS policy를 해결하기 위함이었다. 이렇게 하나하나 모든 것들을 작성해주는 것은 무척 성가시고 어려운 일이다. 결론부터 말하자면 CORS 역시 라이브러리가 존재한다. 라이브러리를 사용하면 이러한 CORS 문제를 보다 쉽게 해결할 수 있으므로 앞으로는 라이브러리를 사용하도록 하자.
우선 npm install cors 를 통해 cors 라이브러리를 설치해준다.
설치를 완료했다면 아래의 코드를 작성해주면 된다.
const cors = require('cors')
app.use(cors({
origin: true,
credentials: true
}))
// res.setHeader('Access-Control-Allow-Origin','*')
// res.setHeader('Access-Control-Allow-Methods','POST, GET, OPTIONS, DELETE') // methods 사용여부
// res.setHeader('Access-Control-Allow-Credentials','true')
// res.setHeader('Access-Control-Allow-Headers','Content-type')
라이브러리를 사용함으로써 주석 처리된 내용들을 cors( )가 처리해주게 된다. cors( ) 역시 객체 형태로 옵션값을 전달할 수 있는데 옵션값을 설정함으로써 각각 다른 처리를 해줄 수도 있다.
요약하자면,, CORS policy 이슈를 해결하고자 할 때는 cors 라이브러리를 사용하도록 하자.
'Node > Express' 카테고리의 다른 글
Node.js - express (18) multer (2) (0) | 2022.03.15 |
---|---|
Node.js - express (17) multer (1) (0) | 2022.03.15 |
Node.js - express (15) Ajax - fetch , axios (0) | 2022.03.07 |
Node.js - express (14) Ajax - XMLHttpRequest( ) (0) | 2022.03.06 |
Node.js - express (13) JWT 로그인 인증 (0) | 2022.03.05 |