[python 2.x] Flask 및 Django 등에서 CORS해결하기
초급 개발자가 서버 사이드, 클라이언트 사이드로 구분해서 개발하는경우 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
덕분에 해결했습니다. ㅠㅠ