[python 2.x] Flask 및 Django 등에서 CORS해결하기

글쓴이 Engineer Myoa 날짜

초급 개발자가 서버 사이드, 클라이언트 사이드로 구분해서 개발하는경우 CORS문제에 봉착하기 쉽다.

W3C에서 XMLHttpRequest를 통해 HTTP 통신을 요청하려면 도메인이 완전히 일치해야만이 요청할 수 있도록 규정하였다.

이는 Basic한 JSP나 PHP, Linked Chain기반 HTML문서를 제외하고

현 메타로 사용되는 SPA(Single Page Application)이나 Server-Side <-> Front-Side 개발방식에서는 아주 쥐약이 된다.

따라서 이를 해결하기위해 여러 개발자들이 같은 도메인(Same Domain Origin)이 아니라 다른 도메인(Cross-Domain Origin)에 대해서도

XMLHttpRequest를 허용해달라고 요청한바 W3C에서는 이를 CORS(Cross Origin Resource Sharing)라는 방법으로 해결안을 제시하였다.

이름에서 알 수 있듯이 다른 도메인으로부터 Resource를 끌어다 사용할 수 있는 방안인데 API요청도 하나의 GET, POST등의 메소드이기 때문에

이 CORS 권고안을 이용하여 해결이 가능하다.

 

일단 Same Origin이라는 용어의 근본을 알아보자

 

아래는 모질라제단의 Same Origin Policy에 관해 설명되어있는 문서이다.

https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy

 

일부를 발췌해보면

 

 

IE를 제외하고는 HTTP POST, GET, PUT, DELETE 요청시 완벽하게 같은 도메인에서만(Same Origin) 요청을 승낙받을 수 있다.

http는 https로 요청할 수 없고, 같은주소라도  port가 다르면 다른 호스트로 판단하여 Same Origin Policy를 위배, 요청할 수 없게 된다.

따라서 같은 도메인(호스트)의 요청 외에 대해서는.. 요청에 대한 결과를 거부받는게 아니라 요청 자체를 거부받는것이다.

IE에서만 포트를 제외하고 같으면 되는방향으로 비표준이 적용되었다.

 

그렇다면 이를 어떻게 해결할 수 있나?

보통 CORS에 대한 솔루션은 Server-Side에서 진행해주는것이 맞다.

Front-End에서 Back-End로 POST메서드를 요청했다고 하자.

상황에 따라 OPTIONS메서드가 먼저 요청됐을수도 있다.

 

Back-End입장에서는 너는 나와 다른 Origin(도메인, 호스트)이기 때문에 메서드에 대한 요청을 거부한다. 이를 보통 OPTIONS 메서드에서 처리해준다.

라고 판단을 내린다.

따라서 Server-Side에서 해결하려면 사용하는 Framework에 맞는 솔루션을 이용한다.

python기반의 웹프레임워크인 Django와 웹마이크로프레임워크인 Flask에서는 cors라는 패키지가 존재한다.

간단하게 테스트하기 위해 flask와 flask httpauth, flask cors 패키지를 이용하여 구현해보겠다.

 

1. Server-Side (Python 2.x, Flask)

main.py

# -*- coding: utf-8 -*-

from flask import Flask, jsonify
from flask_httpauth import HTTPBasicAuth

from flask_cors import CORS, cross_origin


app = Flask(__name__)
auth = HTTPBasicAuth()

#CORS(app)
users = {"myoa" : "test"}


@auth.verify_password
def getPwd(username, password):
	if username in users:
		if users[username] == password:
			return users.get(username)
	return None

@app.route("/api/v1.0/loginToken", methods= ["GET", "OPTIONS"])
#@cross_origin()
@auth.login_required
def cors_test():
	returnData = {"msg" : str(auth.username()) }
	return jsonify(returnData)

if __name__ == "__main__":
	app.run(host="127.0.0.1", port="5000")

 

2. Client-Side (HTML, JS, AngularJS)

index.html

<!DOCTYPE HTML>
<html ng-app="cors">
	<head>
		<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
		<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-route.min.js"></script>
		<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-resource.min.js"></script>

		<script src="js/core.js"></script>
	</head>
 <body ng-controller="web_cors">
   <input ng-model="loginId" value="bar">
	 <input ng-model="loginPwd" value="bar">
   <button ng-click="runAuth(loginId,loginPwd)">{{buttonText}}</button>

 </body>
</html>

 

core.js

var myApp = angular.module('cors', ['ngRoute','ngResource']);

var serverURL = "http://127.0.0.1:5000/";

myApp.controller('web_cors', function ($scope, $http, $resource) {
  // Controller magic
  $scope.buttonText = "post!";
  $scope.postData = "test";

  $scope.runAuth = function(lId, lPwd){
      if (lId == null || lId == "" || lPwd == null || lPwd == ""){
          sweetAlert("경고","ID또는 비밀번호가 비었습니다.","warning");
          return;
      }
      $http.defaults.headers.common = {}; //you probably don't need this line.  This lets me connect to my server on a different domain
      $http.defaults.headers.common['Authorization'] = 'Basic ' + btoa(lId + ":" + lPwd);

      var redirect = function(){
         $window.location.href = 'index.html';
     };
      $http({method: 'GET', url: serverURL+ 'api/v1.0/loginToken'
      }).then(successCallback, errorCallback);

    };

    function successCallback(response){
      console.log("success...");

      console.log(response);
    }
    function errorCallback(error){
      console.log("err...");
        console.log(error);
    }



});

 

이제 Back-end 서버를 열어주고, Front-end쪽도 deploy해주면 접속이 가능해진다.

 

아래는 접속한 모습이다.

 

※ 테스트를 위해 localhost loopback IP를 이용하면 CORS문제가 발생하지 않을 수 있다. 따라서 나의 경우는 API서버만 PC의 IP를 사용하였다.

위에서 볼 수 있듯이 IE에서는 port만 다른경우는 CORS가 허용되기 때문.

(Front-End = localhost, Back-End = PC Private IP)

 

 

이제 각 text필드에 id와 pw를 가정하고 서버로 전송할텐데, 지금 현재는 CORS가 주석처리되어 미동작하고있다.

결과를 살펴보자.

XMLHttpRequest상에서 Access Control Allow Origin이 헤더에 포함되어있지 않기때문에 엑세스가 거부되어 요청할 수 없다고 나온다.

 

 

웹페이지 새로고침없이 Back-end 서버만 CORS를 적용하여 다시 기동한다음 시도해보았다.

 

 

CORS적용은 여러방법이 있는데 (주로 3가지) 자주 사용하는 것은

 

1. app = Flask(__name__)로 app을 만들었다면

CORS(app)로 감싸는 방법.

 

2. CORS처리가 필요한 API메서드 위에 데코레이터로 @cross_origin을 넣어주는 방법

 

위에 적혀있는 코드에서 #CORS(app) 또는 #@cross_origin 부분의 주석을 해제하고 재기동하면 된다.

 

이전까진 계속 CORS문제로 요청을 수행할 수 없었다가

Back-End에 CORS적용을 해주고 요청하니 바로 success메시지를 console에 출력한다.

 

 

이렇게 CORS문제를 쉽게 해결할 수 있으나 중요한 사항이 있어 첨언한다.

Django나 Flask를 API 서버로 활용하다보면 버전관리때문에 Blueprint(청사진)을 이용하는 경우가 대다수이다.

이 때 CORS를 적용하기 위해서는 가장 큰 app에 CORS처리를 하던가,

아니면 Blueprint로 나뉘어져있는 각 api app들을 CORS처리하던가,

그것도 아니면 마찬가지로 필요한 API메서드에만 @cross_origin데코레이터로 처리해주면 된다.

 

 

수고하셨습니다.

카테고리: 개발노트

1개의 댓글

궁금이 · 2019-11-25 11:58:45

덕분에 해결했습니다. ㅠㅠ

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다