사건의 발단

사내 관리자 페이지에서, 현재 운영중인 모든 API를 테스트 할 수 있는 기능을 추가하고 있었다.

Swagger를 사용하지 않고, 사내 API를 테스트 해볼 수 있는 페이지였다.

사내에서 관리하는 API 서버가 여러개이고, 로그인 인증까지 해야되서 복잡한 상황이었다.

프론트에서 api를 호출하는 방식을 사용하던중, 이때까지는 전혀 문제가 없었던 CORS에러가 나오기 시작했다.

 

웹개발자라면 한번은 봤을 CORS 에러

 

CORS 에러

먼저 CORS에러가 무엇인지 알아보자

출처 : https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

"교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.

웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행합니다.

 

보안 상의 이유로, 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한합니다.

예를 들어, XMLHttpRequest와 Fetch API는 동일 출처 정책을 따릅니다. 즉, 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 합니다. " 

(출처: MDN)

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

위 내용을 정리해보자면

- CORS는 서로 다른 출처(Origin) 간에 리소스를 전달하는 방식을 제어하는 체제이며,

- CORS 요청이 가능하려면 서버에서 특정 헤더인 Access-Control-Allow-Origin과 함께 응답할 필요가 있다.

 

그러므로 CORS 정책을 위반하여 서로 다른 출처를 가진 상태에서 무언가를 요청하게 되면 브라우저가 보안 상의 이유로 차단을 해버린다!

 

EX) 클라이언트 포트가 3000번이고 서버의 포트가 8000번, 클라이언트(3000)에서 서버(8000)로 리소스를 요청했을 때 CORS 에러 메시지가 클라이언트 콘솔에 빨갛게 뜨고 데이터를 주지 않게 된다.

 

왜냐? 3000번 포트에서 3000번이 아닌, 8000번 포트로 API를 호출했으니, 보안상의 문제가 있는 줄 알고 차단한 것이다.

 

결론 : CORS 문제를 해결하려면 동일한 출처에서 리소스를 요청하면 된다.

 

 

해결과정

 

1. CORS 에러가 뜬 상황

참고 : maca-admin port: 31010 / maca-admin-api port: 31000

 

여기서 여기저기서 구글링을 하니 해야되는 것은 3가지로 압축됐다.

  1. 프론트엔드에서 Access-Control-Allow-Origin을 허용받게 처리하기
  2. 백엔드에서 Response 헤더의 Access-Control-Allow-Origin를 True로 만들어주기
  3. 프론트에서 로그인정보를 cookie에 넣어 보낼 수 있도록 처리

 

2. Cors 처리 시작 (feat. flask-cors)

에러메시지를 보면 request’s credential을 하려면, Acccss-Control-Alllow-Origin header가 *이면 안된다고 한다.

그리고 XMLHttpRequest가 withCredentials 특성에 의해 컨트롤 된다고 한다.

 

아직 잘 모르겠지만 해야될 것은 두가지인 것 같다.

 

Access-Control-Allow-Origin header의 값을 *(전체) 말고 내 프론트 주소 (31010포트)로 바꿔주자 (위에서 말한 1번, 2번)

withCredential이 어떤 놈인지 찾아보고, 해야될게 있나 찾아보자. (위에서 말한 3번)

 

3. Access-Control-Allow-Origin 설정 & withCredential 처리

나는 백엔드가 Flask라서 찾아보니, flask_cors라는 라이브러리를 통해 처리가 가능하다고 한다. (공식문서)

pip install flask-cors

그리고 어떻게 설정하는지 찾아보니 Flask 프로젝트에서 app=Flask(__name__)을 해주는 곳 뒤에 코드를 추가하라고 한다.

 

그래서 api repository 최상위에 있는 __init.py__ 에 코드를 추가해준다.

17번, 25번 줄을 추가해주었다.

25번 줄을 추가할때 /bo/는 api 제일 앞 형식에 맞게, ‘origins’에는 허용할 웹페이지 주소를 넣는다.

(나는 프론트 포트가 31010이라서 저렇게 넣었다.)

 

만약 여러 포트를 허용하고 싶다면,

'origins':["http:naver.com", "http:hello.com"]

이렇게 리스트 안에 넣어주면 된다.

 

그리고 우연히 공식문서를 보던중 아까 말한 withCredential에 대한 해결책도 찾았다.

https://flask-cors.readthedocs.io/en/latest/api.html?highlight=supports_credentials#using-cors-with-cookies

CORS(app)을 해줄때, supports_credentials=True를 함께주면, 쿠키를 함께 넘겨줄 수 있다는 뜻이다.

 

만약 cookie를 넘겨주지 않아도 되면, 설정하지 않아도 된다. (나는 cookie로 로그인 정보를 넘겨줘서 설정)

다른 언어에서는 'withCredential'이라고 하는데, 여기서는 'supports_credentials'라고 한다.

 

자... 이제 설정도 끝났으니 완벽하게 되겠지?!

 

4. 두번째 시련 시작

어림도 없지. 아래와 같은 에러가 또 떴다.

에러메시지를 읽어보니, response의 Access-Control-Allow-Origin 헤더가 값이 한개여야 되는데, 두개가 들어왔다는 것.

 

그래서 다른 API 호출 한것을 보니

Credentials는 True로 제대로 됐지만, access-control-allow-origin에 값이 두개가 들어가 있다.

 

flask-Cors 공식문서에 따르면 supports_credentials를 설정하면 access-control-allow-origin이 *이 되면 안된다고 한다.

그래서 access-control-allow-origin : *을 지워보려고 한다.

 

하지만 아무리 프로젝트에서 관련 코드를 찾아봐도 관련 정보가 없었다. (CORS관련 설정이 없음)

그렇게 삽질을 하다가 찾아낸 곳은 바로 Nginx 였다.

docker의 Nginx 설정을 찾다보니

사진과 같이 모든 header에 *이 붙이는 코드가 있었다. (이놈이 문제!!)

그래서 이걸 #을 붙여 주석처리해주자! (참고 : 만약 권한이 없다고 뜨면, root로 나와서 해보자)

 

5. 해치웠나?

설정 값을 바꾼 후 Nginx를 reload해주고, uwsgi도 다시 실행시켜 준다.

드디어 실행이 된다.

 

가장 처음에 정리한 해야될 3가지를 살펴보면, 

  1. 프론트엔드에서 Access-Control-Allow-Origin을 허용받게 처리하기 (O)
  2. 백엔드에서 Response 헤더의 Access-Control-Allow-Origin를 True로 만들어주기 (O)
  3. 프론트에서 로그인정보를 cookie에 넣어 보낼 수 있도록 처리 (O)

이렇게 CORS 처리의 여정이  끝났다.

 

결론 : 

CORS에러는 프론트와 백 두곳 모두 설정 값을 바꿔줘야 한다.

그리고 정말 안되면, NGINX의 설정을 보자!!

+ Recent posts