본문 바로가기
Archive

React와 node로 알아보는 내가 겪었던 필수 웹 지식 - CORS, Cookie, Post요청, headers, file전송

by livemehere 2022. 7. 30.

CORS

Cross-Origin Resource Sharing 이다.

말그대로 서로다른 도메인간의 자원 공유를 뜻하는데, 기본적으로 금지되어있다.

그렇기 때문에, 서버와 클라이언트가 서로다른 도메인에있다면 자원을 제공하는 측인 서버가 자신의 자원에 접근할 수 있도록 허용해주어야 한다.

 

단순한 GET요청부터 막히는데, header에 Access-Control-Allow-Origin 를 와일드카드(*)로 등록해줌으로써, 서버의 자원은 모든 도메인에서 사용가능하다. 보안을 위해서라면, 정확히 클라이언트의 도메인을 지정해주는 것이 중요하다.

그리고 cors는 브라우저에서만 발생하기 때문에, 앱이나 웹이 아닌 다른곳에서는 발생하지 않는다.

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*"); // cors, 다른 도메인에서의 접근 허용
  next();
});

app.get("/", (req, res) => {
  res.json("server on!");
});

 

헤더에 담는 값들은 그냥 string일 뿐이다.

*, GET, POST .. 등등 약속된 키워드들은 그대로 인식을한다. 하지만 그렇다고 다른정보를 담지 못하는것은아니다.

아래처럼 막넣어도되긴한다. (실험해보고싶었다..)

예시

Cookie

요청에 담겨있는 cookie를 읽어보면 undefined가 나온다.

app.get("/", (req, res) => {
  console.log(req.cookies); //undefined
  res.json("GET /");
});

 

 

express에서는 쿠키를 쉽게 읽기 위해서 미들웨어를 별도로 추가해주어야한다.

기존에는 express 패키지에 포함되어있었는데, 분리되어서 설치해야한다.

npm i cookie-parser

 

 

그러면 이제 req.cookies를 통해서 쿠키를 읽을 수 있고, 현재는 브라우저에 쿠키가 없기때문에, 빈 객체상태이다.

app.use(cookieParser());
app.get("/", (req, res) => {
  console.log(req.cookies); //[Object: null prototype] {}
  res.json("GET /");
});

 

 

 

쿠키는, Key, value, options(선택) 을 함께 전달해주면되는데, 이상태로는 브라우저에 저장되지 않는다.

 

app.get("/", (req, res) => {
  res.cookie("cookieName", "cookieValue");
  res.json("GET /");
});

 

 

 

앞전에 서버가 클라이언트에게 자원을 허용하듯, 쿠키를 브라우저에 전달하고 저장하는 행위는 브라우저가 허용해야한다.

클라이언트의 request에 옵션을 추가해준다. (요청에 쿠키 정보를 함께 보내겠다는 의미이다)

  fetch("http://localhost:3030", {
      credentials: "include",
    })

 

 

 

 

그리고서 요청을 하게되면, 서버에서 브라우저의 쿠키에 접근이 가능하게 되어, 원하는데로 브라우저에 쿠키를 저장하도록 응답을 보내어, 아래 사진과 같이 브라우저에 잘저장된 것을 확인할 수 있다.(기본적으로 만료기간이 Session 으로 되어있다.)

 

 

 

응답에서 쿠키를 담아서 보내주기 때문에, 전달된 요청 객체에서 조회가 가능하다.

app.get("/", (req, res) => {
  console.log(req.cookies); // { cookieName: 'cookieValue' }
  res.json("GET /");
});

 

 

쿠키에 만료기간을 설정하면, 그 기간후에 자동으로 제거된다.

추가로 http only 옵션을 넣어주어, javascript로 조작하지 못하도록 보안설정을 할 수 도있다.

res.cookie("name", "kong", { maxAge: 1000 * 3 });

 

 

WildCard 문제

아마 위처럼 서버에서 Origin 허용을 * wild card로 해두고서, 클라이언트에서 credetial을 포함해서 요청을 보낸다면,아래 에러와함께 response의 응답값을 버려버린다. (이때 쿠키는 적용이된다.)

 

 

이때 쿠키가 탈취당할 위험이 있기 때문이다.

 

해결방법

결론적으로 와일드카드로 요청을 허용해놓은 서버에는 쿠키를 함께 담아서 보낼 수 없다.

cors 허용을 지정하거나, 아래와같이 요청의 오리진을 그때그때 허용해주고, Access-Control-Allow-Credentials 옵션을 true로 설정해주면 된다.

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
  res.setHeader("Access-Control-Allow-Credentials", true);
  next();
});

여기까지 CORS로 인한 서버와 클라이언트간의 통신과, 브라우저와의 통신에서는 cookie를 서로 주고받을 수 있도록 했다.

 

POST 요청의 Body 전달하기

클라이언트에서 post 요청으로 body에 JSON 데이터를 전달하면, 서버에서 받을 수 있어야한다.

🚀 GET요청에는 body를 담을 수 없다. 즉, payload를 보낼 수 없다.
fetch("http://localhost:3030", {
      method: "post",
      credentials: "include",
      body: JSON.stringify({ name: "kong", age: 25 }),
    })
      .then((res) => res.json())
      .then((res) => {
        console.log(res);
      });

 

 

이렇게 fetch 요청을 하면 서버에서는 undefined를 받는다.

json을 파싱할 수 있도록 미들웨어를 추가해주어야한다.

app.post("/", (req, res) => {
  console.log(req.body); undefined
  res.json("POST /");
});

 

이렇게 json 형식을 파싱하도록 해주면 객체로 body를 읽긴하는데 빈객체가 읽힌다.

이유는 요청자체가 JSON인지 header에서 명시해주지 않아서, 서버에서 body가 JSON데이터인지 알 수 가없다.

요청헤더를 수정해야한다.

app.use(express.json());
app.post("/", (req, res) => {
  console.log(req.body); // {}
  undefined;
  res.json("POST /");
});

 

 

이렇게 변경해주면 된다.

 fetch("http://localhost:3030", {
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ name: "kong", age: 25 }),
    })
      .then((res) => res.json())
      .then((res) => {
        console.log(res);
      });

 

 

 

그러면 또 아래와같은 에러가 반환된다.

Access to fetch at 'http://localhost:3030/' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

내용은 Content-Type 이라는 헤더의 사용이 허용되지 않는다는 내용이다.

서버에서는 특정 헤더들을 허용하는 설정을 해줄 수 있는데 아래와 같이 할 수 있다.

res.header("Access-Control-Allow-Headers", "Content-Type");

그러면 성공적으로 서버에서 body를 읽을 수 있다.

app.post("/", (req, res) => {
  console.log(req.body); // { name: 'kong', age: 25 }
  undefined;
  res.json("POST /");
});

 

Form 태그로 post 보내기

아래와 같이 ajax 요청이 아니라, html form 태그를 이용해서 Post 요청을 하는 경우는 또 다르다.

서버에선는 또다시 {} 빈객체를 읽는다.

<form action="http://localhost:3030" method="post">
        <input type="text" name="username" />
        <input type="text" name="password" />
        <button type="submit">SEND</button>
</form>

 

서버에서 아래의 미들웨어를 추가해주어서, form 태그로 요청된 body도 읽을 수 있도록 설정해준다.

app.use(express.urlencoded({ extended: true }));
This object will contain key-value pairs, where the value can be a string or array (when extended is false), or anytype (when extended is true).

extended는 false일 경우 string, array형태만 파싱하고, true인 경우에는 모든 형태를 파싱한다.

 

File 전송하기

요청은 아래와 같이 formData에 담아서, header의 content-type을 multipart/form-data 로 설정해준다.

🚀 formData로 전송하게되면, 자동으로 Content-Type을 아래와 같이 담아서 보내기 때문에, 별도의 Content-Type를 지정해주면 서버에서 제대로 파싱하지 못한다. 그렇기 때문에 file데이터는 를 전송할때는 Content-Type은 비워야한다.

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryyPBBgNnyNPsHLcRh
const formData = new FormData();
    formData.append("file", file);
    formData.append("name", "kong");
    fetch("http://localhost:3030/upload", {
      method: "post",
      body: formData,
    });

 

 

그러면 또 서버에서는 빈 객체를 받는다.

이쯤되니, express는 정말 모든걸 직접 설정해주어야한다.

기본적인 json형태의 body, form 태그를 이용한 encoded data, file.. 모두

file을 파싱하기 위한 미들웨어를 장착해주어야한다.

npm install --save multer
multipart/form-data 를 파싱해주는 미들웨어이다.

 

 

서버는 아래와같이 요청에 미들웨어를 장착해주고, dest에는 전송받은 파일이 저장될 곳을 지정하면된다.

지금처럼 설정해두면 /upload에 파일을 보내면 서버의 upload 디렉토리에 파일이 그대로 저장된다.(확장자제외)

그리고 request객체에 file을 추가해준다.

const multer = require("multer");
const upload = multer({ dest: "upload" });

app.post("/upload", upload.single("file"), (req, res) => {
  console.log(req.body);
  console.log(req.file);
});

/*
[Object: null prototype] { name: 'kong' }
{
  fieldname: 'file',
  originalname: 'á\x84\x89á\x85³á\x84\x8Fá\x85³á\x84\x85á\x85µá\x86«á\x84\x89á\x85£á\x86º 2022-07-29 á\x84\x8Bá\x85©á\x84\x92á\x85® 3.42.52.png',
  encoding: '7bit',
  mimetype: 'image/png',
  destination: 'upload',
  filename: 'd7bf77dc9d58fad0ee972bf776fe873f',
  path: 'upload/d7bf77dc9d58fad0ee972bf776fe873f',
  size: 6099
}

*/
위처럼 formData에 파일 분만아니라 일반 body에 들어가는 key, value값들도 함께 넣으면, body에 보존이되고, file만 별도로 분리가된다.

 

파일 여러개 동시에 전송하기

formData에는 같은키에 append를 하게되면 배열로 변환되어 추가된다.

const formData = new FormData();

    for (let i = 0; i < file.length; i++) {
      console.log(file[i]);
      formData.append("file", file[i]);
    }
    fetch("http://localhost:3030/upload", {
      method: "post",
      body: formData,
    });

 

 

서버에서는 upload 미들웨어를 array로 바꾸어주고, req.files를 참조하면 된다.

app.post("/upload", upload.array("file"), (req, res) => {
  console.log(req.files);
});

 

 

 

그런데 이런 확장자가 없는 형태로 저장되기 때문에, 사용하기가 어렵다.

req.file, req.files 에 전송된 파일에대한 이름, 확장자, 사이즈에대한 정보가있지만, upload 미들웨어를 통과한 후기때문에 파일명을 변경해주려면 이미 저장된 파일에 접근하여 변경해주어야하는데,

이방법 보다는 multer의 설정을 변경해주어, 저장되기전에 수행할 동작을 정의해주면된다.

const upload = multer({
  storage: multer.diskStorage({
    destination: (req, file, cb) => {
      cb(null, "upload/");
    },
    filename: (req, file, cb) => {
      cb(null, Date.now() + file.originalname);
    },
  }),
});

multer에서 dest를 바로설정하지않고, storage 옵션에다가 diskStorage를 만들어서 저장소와, filename을 설정해줄 수 있다.

아래와 같이 originalFilename을 유지한체로 저장할 수 있다.

한글파일의 경우 다 깨지기 때문에, 그대로저장하는것은 별로다..

 

무조건적으로 파일시스템에 저장하지 않도록 할 수 도 있다.

아래처럼 메모리에 저장하도록하면, req에서만 담에서 처리할 수 있다.

보통 s3나 외부 파일저장 시스템에 연계할 때 사용한다.

const memoryStorage = multer.memoryStorage();

const upload = multer({
  storage: memoryStorage,
});

 

cors 미들웨어로 한방에 해결하기

npm i cors
app.use(cors());

 

위처럼하면 간단히 끝이지만, credentials:'include' 속성, 즉 쿠키를 같이보낼때는,

아래와 같이 두개의 옵션을 올려주어야하는데, origin은 와일드카드가안된다.

직접 지정하거나, 위에서 했던것 처럼 미들웨어에서 직접 헤더를 매번 승인해주면된다.

app.use(
  cors({
    credentials: true,
    origin: "http://localhost:3000",
  })
);
반응형

'Archive' 카테고리의 다른 글

TS 냐금냐금 - 6  (0) 2022.07.30
TS 냐금냐금 - 4  (0) 2022.07.30
TS 냐금냐금 - 3  (0) 2022.07.29
멋쟁이 사자처럼 해커톤을 마치며  (0) 2022.07.28
TS 냐금냐금 - 2  (0) 2022.07.25