코드 캠프 17일차) CORS? Database? 😇

민겸·2022년 9월 22일
0

코드캠프_FE09

목록 보기
9/28
  1. CORS
  2. Database
  3. SQL, NoSQL

열 받게😡 하는 CORS

프론트 엔드 개발을 해봤다면, 최소한 한 번쯤은 겪었을 CORS에 대해 알아보자.

CORS는 Cross-Origin Resource Sharing의 약자로, 우리나라말로는 교차 출처 자원 공유이다. 하나의 출처에서 실행중인 웹이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 알려주는 체제이다.

아~ 그렇구나~ 가 아니라 그래서 출처가 뭔데?

출처(Origin)란

우리는 어떤 사이트를 접속할 때 해당 사이트의 URL을 통해 접속하게 된다.
위의 그림에서 알 수 있듯이, protocol, hostname 그리고 port를 통틀어 Origin이라고 한다. 그리고 자바스크립트에서도 location.origin을 통해 출처를 알 수 있다.웹은 리소스의 출처가 자신의 출처와 다를 때 교차 출처 HTTP 요청을 실행하게 된다.

예를 들면, https://A.comhttps://B.com 이 있다고 가정했을 때,
https://A.com의 프론트 엔드 JS 코드가 XMLHttpRequest를 사용해 https://B.com/data.json을 요청하는 경우가 있다.

기본적으로 보안 상의 이유 때문에 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한한다. Fetch APIXMLHttpRequest가 그러하다. 이 둘은 SOP 정책을 따르기 때문이다.

SOP 정책이란?
Same Origin Policy의 약자로, 동일 출처 정책이다.
예전엔 origin에 해당하는 서버 주소로만(즉, 같은 origin을 가진 주소에만) 요청할 수 있게 하는 정책이 있었다.
예) naver.com 은 naver.com 서버 주소로만 데이터 요청 가능

🤔 웹의 흑역사인 IE 브라우저는 웃기게도 출처 비교 시 Port 부분은 무시한다. 이는 곧 보안 취약으로 이어지며 왜 그렇게 욕을 얻어먹는지에 대한 이유중에 하나이기도 하다.

아니 죄다 다 막아버리면 인터넷이 돌아가?

SOP정책이 너무 타이트해서 CORS정책이 도입되었다.
앞서 말했듯이, 타 사의 서버 주소(다른 출처)에도 데이터를 요청할 수 있게 해주는 정책이다. SOP 정책을 위반해도, 데이터를 가지고 있는 서버에서 CORS 허용을 해주면 클라이언트 쪽에서 요청한 데이터에 대한 응답을 받을 수 있다.
예) Open-API

CORS는 브라우저에만 적용되는 것으로, 출처 비교부터 차단까지 브라우저에서 처리한다. 이 말은 출처를 비교하는 로직이 브라우저에 구현된 스펙이라는 것이다. 서버에서는 정상적으로 응답을 보내주지만 브라우저가 이 응답을 분석해서 동일 출처가 아니면 시뻘건 에러를 내뱉는 것이다. 브라우저와 달리 모바일이나 서버에서 타 서버에 데이터 요청을 하면 CORS와는 상관없이 데이터를 받아올 수 있다.

Proxy Server

가끔 CORS 거부가 되어있는 Open API들이 있는데, 그럴 땐 다른 서버에 데이터 요청을 대신 시키고 그 서버에서 받아올 수 있다. 이 다른 서버를 proxy server라 부른다.
쉽게, A라는 클라이언트에서 B라는 서버의 데이터가 필요해서 데이터를 요청하려는데 CORS 정책에 막혔다. 그래서 A 클라이언트는 접근이 가능한 C 서버에 B의 요청을 대신 받아오도록 요청하고 B 서버는 C서버에서 데이터를 받아온 뒤, 다시 A에게 데이터를 보내준다.

정확히 어떤 보안상의 이유인가?

왜 하필 브라우저 단에서 CORS 조건에 따라 허용/차단을 하는가? 브라우저단에는 쿠키라는 데이터 저장 공간이 있는데, 클라이언트에서 데이터 요청을 하게 되면 이 쿠키가 자동으로 데이터 요청을 따라 다닌다. 그러면 데이터 요청으로 받아온 데이터가 쿠키로 담길 수 있고, 보안상의 이유로 그것을 막기 위해 브라우저단에서 자체적으로 요청을 차단시키도록 되어있다.

모든 요청이 CORS를 사용하는 건 아니다!

CORS 요청을 사용하는 것들은 아래와 같다.

  • XMLHttpRequest와 Fetch API 호출
  • CSS내에서 @font-face에서 교차 도메인 폰트 사용 시에
  • WebGL 텍스쳐
  • drawImage()를 사용해 캔버스에 그린 이미지/비디오 프레임
  • 이미지로부터 추출하는 CSS Shapes

CORS 작동 방식

MDN을 살펴보면, CORS가 크게 3 가지 시나리오로 나뉘어 작동하게 되는 걸 알 수 있다.

1 . 예비 요청 (Preflight)

브라우저는 요청을 보낼 때, 한 번에 보내지 않고 먼저 예비 요청을 보내서 서버와 잘 통신되는 지(안전한 요청인지) 확인한 후 본 요청을 보낸다.
이 요청은 서버에서 추가 정보를 판별하는데 사용하는 HTTP/1.1 Method인 OPTIONS라는 요청이 사용되는 것이 특징이다.
다음은 Preflight request/response 예시이다.

1 | OPTIONS /resources/post-here/ HTTP/1.1
2 | Host: bar.other
3 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) 4 Gecko/20100101 Firefox/71.0
5 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
6 | Accept-Language: en-us,en;q=0.5
7 | Accept-Encoding: gzip,deflate
8 | Connection: keep-alive
9 | Origin: http://foo.example
10| Access-Control-Request-Method: POST
11| Access-Control-Request-Headers: X-PINGOTHER, Content-Type
12|
13|
14| HTTP/1.1 204 No Content
15| Date: Mon, 01 Dec 2008 01:15:39 GMT
16| Server: Apache/2
17| Access-Control-Allow-Origin: https://foo.example
18| Access-Control-Allow-Methods: POST, GET, OPTIONS
19| Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
20| Access-Control-Max-Age: 86400
21| Vary: Accept-Encoding, Origin
22| Keep-Alive: timeout=2, max=100
23| Connection: Keep-Alive

1~11행까지가 브라우저가 서버로 보내는 예비 요청이다.
Origin 헤더에 자신의 출처를 넣고,
Access-Control-Request-Method 헤더에 실제 요청에 사용할 메서드를 설정하고,
Access-Control-Request-Headers 헤더에 실제 요청에 사용할 헤더들을 설정한다.

14~23행까지가 예비 요청에 대한 서버의 응답이며, 어떤 것을 허용하고 어떤 것을 금하고 있는 지에 대한 정보를 담아 보내준다.
Access-Control-Allow-Origin 헤더에 허용할 출처들의 목록을 설정하고,
Access-Control-Allow-Methods 헤더에 허용할 메서드들의 목록을 설정하고,
Access-Control-Allow-Headers 헤더에 허용할 헤더들의 목록을 설정하고,
Access-Control-Max-Age 헤더에 해당 예비 요청이 브라우저에 캐시될 수 있는 시간을 초 단위로 설정한다. 갑자기 웬 캐시랑 시간이냐고? 아래에서 후술할 거임...!

이후에 브라우저는 보낸 예비 요청과 요청에 대한 응답의 정책을 비교하고 안전한지 확인 후 본 요청을 보낸다. 여기서 정책이 다르다면 요청이 CORS 정책을 위반했다고 판단하고 응답을 버린 뒤 에러를 내뱉는다.

예비 요청과 캐싱
예비 요청을 보냄으로써 보안이 강화되는 것은 분명 장점이지만, API 요청을 할 때 마다 예비 요청을 하게 되면서 요청 수가 배로 늘어나게 되고 이는 비용적인 측면에서 좋지 않다. 그리고 예비 요청을 거쳐야 하기 때문에 요청의 총 시간이 늘어나게 되어 웹 성능에 영향을 줄 수 있다.
따라서 브라우저 캐시를 이용해 Access-Control-Max-Age 헤더에 캐시될 시간을 명시해주면, 이 예비 요청을 캐싱해 최적화 시킬 수 있다.

예비 요청 캐싱 시간은 파이어 폭스는 최대 86400초(24시간)까지, 크로미움 기반 브라우저는 7200초(2시간)까지 가능하다.

예비 요청 캐시는 여느 다른 캐싱과 비슷하게 작동한다.

  1. 브라우저는 예비 요청을 할 때 마다, 예비 요청 캐시를 확인하고 해당 요청에 대한 응답이 있는지 확인한다.
  2. 캐싱된 응답이 없다면, 서버에 예비 요청을 보낸다.
  3. 여기서 만약 서버로부터 온 응답에 Access-Control-Max-Age 헤더가 있다면, 해당 시간 동안 브라우저 캐시에 응답 결과를 저장한다.
  4. 다시 요청을 보내고, 요청에 대한 응답이 캐싱되어 있다면 예비 요청을 서버로 보내는 대신 캐싱된 응답을 사용한다.

2. 단순 요청

단순 요청은 말 그대로 예비 요청을 생략하고 서버에 직행으로 본 요청을 보내면 서버가 이에 대한 응답으로 헤더에 Access-Control-Allow-Origin 헤더를 보내주면 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다.
일부 요청은 CORS Preflight를 트리거 하지 않기 때문에 가능한 일이다. 보안을 강화하는 Preflight가 없는 대신 다음과 같은 특정 조건을 충족해해야만 한다.

  1. 요청 메서드가 GET, HEAD, POST 중 하나여야 한다.
  2. Accept, Accept-Language, Content-Language, Content-Type 헤더일 경우에만 적용된다.
  3. Content-Type 헤더에는 다음 값들만 허용된다.
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  4. 요청에 사용된 XMLHttpRequestUpload 객체에는 이벤트 리스너가 등록되어 있지 않아야 한다. 대신 XMLHttpRequest.upload 프로퍼티를 사용해 접근한다.
  5. 요청에 ReadableStream 객체가 사용되지 않아야 한다.

단순 요청이 일어나는 상황은 정말 드물고 거의 preflight가 이루어진다고 보면 된다. 왜냐하면 대부분의 HTTP API 요청은 text/html이나 application/json으로 통신하기 때문에 3번 정책에 위반되기 때문이다.

3. 인증된 요청

XMLHttpRequest나 Fetch를 사용할 때 CORS에 의해 드러나는 가장 흥미로운 기능은 credentialed requests이다. 이 credentialed request는 HTTP Cookies와 HTTP Authentication 정보를 인식한다. 기본적으로는 cross-site XMLHttpRequest나 Fetch 호출에서 브라우저의 자격 인증 정보를 보내지 않는다.

여기서 말하는 자격 인증 정보란,
세션 ID가 저장되어있는 쿠키 혹은 Authentication 헤더에 설정하는 토큰 값 등을 일컫는다.

클라이언트

먼저, 클라이언트에서 인증 정보를 보낼 수 있도록 설정하는 법부터 알아보자.
요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션인 credentials가 있다. 이 옵션에는 3가지의 값을 사용할 수 있다.

옵션 값설명
same-origin(기본값)같은 출처 간 요청에만 인증 정보를 담을 수 있다.
include모든 요청에 인증 정보를 담을 수 있다.
omit모든 요청에 인증 정보를 담지 않는다.

셋 중 하나라도 사용하지 않는다면, 쿠키 등의 인증 정보는 서버로 전송되지 않는다.

서버에 인증 요청을 보내는 법은 Fetch 메서드, axios, jQuery 등 다양하다. 어떤 메서드를 사용하냐에 따라 credentials를 설정하는 문법 또한 조금씩 달라진다.

// fetch 메서드
fetch("https://example.com:1234/users/login", {
	method: "POST",
	credentials: "include", // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
    body: JSON.stringify({
        userId: 1,
    }),
})

// axios 라이브러리
axios.post('https://example.com:1234/users/login', { 
    profile: { username: username, password: password } 
}, { 
	withCredentials: true // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
})

서버

서버 또한 이러한 인증 요청에 대해 일반적인 CORS 요청과는 다르게 대응해줘야 한다.

  • 응답 헤더의 Access-Control-Allow-Credentials 항목을 true로 설정해야 한다.
  • 응답 헤더의 Access-Control-Allow-Origin의 값에 와일드카드 문자("*")는 사용할 수 없다.
  • 응답 헤더의 Access-Control-Allow-Methods의 값에 와일드카드 문자("*")는 사용할 수 없다.
  • 응답 헤더의 Access-Control-Allow-Headers의 값에 와일드카드 문자("*")는 사용할 수 없다.

    인증 정보는 민감한 정보이기 때문에 출처를 정확하게 설정해주어야 한다.

CORS 참고 글: https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F

Database / ORM

하나의 웹 서비스를 만들 때, 기본적으로 데스크탑이나 노트북 기기를 최소한 3 대를 산다. Backend 1대, Frontend 1대, DB 1대. 사용자의 브라우저에서 접속하게 되면 프론트에서 페이지 렌더링을 위한 데이터를 사용자의 브라우저에 보내준다. 데이터가 들어가는 부분은 사용자의 브라우저에서 useQuery를 백엔드로 요청하고 백엔드는 받은 요청을 바탕으로 데이터베이스에서 데이터를 꺼내와서 유저의 브라우저에 보내준다.

유저가 언제 들어올 지 모르기 때문에, 이 3대의 컴퓨터는 항상 작동되고 있는 상태여야 한다.

DB의 종류

DB는 크게 두 가지 종류로 나뉜다.
SQL과 NoSQL

SQL

SQL은 표(table)형식으로 된 데이터베이스이다. SQL의 대표적인 특징은 바로 각 테이블의 row(행)가 관계 기반으로 이어질 수 있다 라는 것이다. 그래서 RDBMS(Relational DataBase) 라고 불린다.
예시로 위의 그림을 보면 게시글 table의 row와 회원 table의 row간의 관계를 파악해서 1번 게시글의 회원 정보를 조회할 수 있다.

SQL의 종류로는 MySQL Oracle MsSQL PostgresSQL 등이 있다.

이런 테이블을 만들기 위해 사용되는 SQL Query문이 있다.
ex) select, update, create 등등

NoSQL

NoSQL은 서류봉투(Collection)형식으로 된 데이터베이스이다. NoSQL의 대표적인 특징은 SQL과는 달리 하나의 주제에 대해 여러 테이블로 나누는 게 아닌 하나의 문서로 통합된다. 그래서 DBMS라고 불린다.

SQL의 row가 NoSQL의 document라고 보면 된다.

이런 컬렉션을 만들기 위해 사용되는 NoSQL Query문이 있다.

NoSQL의 종류로는 MongoDB FireBase 등이 있다.

이런 DB를 구성하기 위해 쿼리문을 일일히 쓰는 것을 편하게 해주는 ORM(Object Relation Mapping) ODM(Object Document Model)이 있다. 이 툴을 사용하면 쿼리문을 외울 필요가 없어진다.

ORM의 종류로는 sequalize(js), prisma, typeorm(ts) 등이 있다.
ODM은 Mongoose 등이 있다.

하지만 ORM, ODM에 모든 명령어가 다 있는 것은 아니어서, 없는 명령에 해당하는 쿼리문은 외울 필요가 있다.

참고:
https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F

profile
기술부채상환중...

0개의 댓글