시나몬 브레드
개발과 관련된 이야기를 하는 곳입니다. 쉽게 찾을 수 있는 소개 형식의 글 보다는 실무를 통해 얻어진 깊이있는 정보와 전체 흐름을 이해할 수 있는 지식을 다루려고 노력중입니다.
javascript ajax 크로스 도메인 요청 하기 (CORS)

개요

웹 개발시 자바스크립트로 외부 서버의 경로로 ajax요청을 날리면 에러가 나면서 요청이 실패한다.
웹 브라우저의 콘솔창에 아래와 같은 메시지를 보게 된다.

크롬

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin ‘[요청한 도메인]' is therefore not allowed access.

파이어폭스

교차 출처 요청 차단: 동일 출처 정책으로 인해 [요청한 도메인]에 있는 원격 자원을 차단하였습니다. (원인: 'Access-Control-Allow-Origin' CORS 헤더가 없음).

외부로 요청이 안되는것은 자바스크립트 엔진 표준 스팩에 동일 출처 정책(same-origin policy)이라는 보안 규칙이 있기 때문이다. 


동일 출처 정책(Same-Origin Policy)

웹어플리케이션 보안 모델에서 중요한 개념중 하나가 동일 출처 정책(Same-Origin Policy)이다.

이 정책에 의해서 자바스크립트(XMLHttpRequest)로 다른 웹페이지에 접근할때는 같은 출처(same origin)의 페이지에만 접근이 가능하다. 같은 출처라는 것은 프로토콜호스트명, 포트가 같다는 것을 의미한다. 즉 쉽게 말하면 웹페이지의 스크립트는 그 페이지와 같은 서버에 있는 주소로만 ajax 요청을 할 수 있다는 것이다.


이정책은 javascript의 보안을 위해 꼭 필요하다. 그러나 요즘은 서버에서 REST api만 제공하고, 프론트 웹 서버가 분리되어 개발되는 웹 프로젝트가 늘어나는 등 javascript에서 외부 도메인으로 호출이 필요한 경우가 많아지는 상황에서는 거추장 스러운 기술이 되기도 하고 있다.

그래서 만들어진 추가 정책이 CORS(Cross-Origin Resource Sharing) 이다. 이 정책의 특징은 서버에서 외부 요청을 허용할 경우 ajax요청이 가능해지는 방식이다. CORS에 대해서 설명하기전에 서버의 도움없이 동일 출처 정책(same-origin policy)을 회피하여 외부 서버로 요청을 날릴 수 있는 방법을 몇가지 소개 한다.


1. 웹 브라우저 실행시 외부 요청을 허용하는 옵션을 사용

아래에서 자세하게 설명하겠지만 same origin policy는 결국 클라이언트인 웹 브라우저가 요청을 해도 되는지 판단해서 결정하는 것으로 이 과정만 무시한다면 어디든 요청을 못할 이유는 없다. 크롬같은 웹 브라우저들은 실행시 커맨드라인 옵셥을 통해서 외부 도메인 요청가능 여부를 확인하는 동작을 무시하게 할 수 있다.
크롬의 경우: --disable-web-security 옵션을 추가하여 크롬 실행

2. 외부 요청을 가능하게 해주는 플러그인 설치

이 부분도 아래에서 설명하겠지만 서버에서 받은 요청의 응답에 특정 header(Access-Control-Allow-Origin: *)만 추가하면 웹 브라우저가 요청이 가능한 사이트로 인식해서 요청이 가능하다. 크롬의 경우 웹스토어에 보면 크롬에서 발생하는 모든 http 요청을 가로채서 응답에 위 header를 추가해주는 플러그인이 있다. 웹스토어에서 cors로 검색하면 확장 프로그램 검색결과에서 찾을 수 있다.

1. 2. 번 방식은 웹 브라우저 사용자가 사용하는 브라우저를 직접 셋팅하는 방식으로 개발자라면 활용해 볼 수 있겠지만 일반 사용자가 사용해야 하는 웹페이지라면 적용이 불가능하다고 보면된다.

3. JSONP방식으로 요청

웹 브라우저에서 css나 js 같은 리소스 파일들은 동일출처 정책에 영향을 받지 않고 로딩이 가능하다. 이런점을 응용해서 외부 서버에서 js 파일을 읽듯이 요청한 결과를 json으로 바꿔주는 일종의 편법적인 방법이다. 단점은 리소스 파일을 GET 메서드로 읽어오기 때문에 GET 방식의 API만 요청이 가능하다. 더 궁금하다면 아래 Kingbbode 님의 글을 보기 바란다.
JSONP 알고 쓰자: http://kingbbode.tistory.com/26



CORS (Cross-Origin Resource Sharing)

웹 브라우저에서 외부 도메인 서버와 통신하기 위한 방식을 표준화한 스팩이다. 서버와 클라이언트가 정해진 해더를 통해 서로 요청이나 응답에 반응할지 결정하는 방식으로 교차 출처 자원 공유(cross-origin resource sharing)라는 이름으로 표준화가 되었다. 

교차 출처 자원 공유(cross-origin resource sharing) 방식은 요청을 받은 웹서버가 허용 할 경우에는 다른 도메인의 웹 페이지 스크립트에서도 자원을 주고 받을 수 있게 해준다.

CORS 작동 방식


preflight request (사전요청)

요청하려는 URL이 외부 도메인일 경우 웹 브라우저는 preflight요청을 먼저 날리게 된다.
preflight 요청은 실제로 요청하려는 경로와 같은 URL에 대해 OPTIONS 메서드로 요청을 미리 날려보고 요청을 할 수 있는 권한이 있는지 확인한다.

위와 같이 CORS 요청을 편법없이 하기 위해서는 클라이언트의 처리만으로는 안되고 해당 서버측에서preflight 요청을 처리하는 기능이 추가로 필요하다.  javascript로 http요청을 할 수 있는 방법은 위와같이 동작하는 XMLHttpRequest 객체를 통하는 방법말고는 없다.



서버에서 CORS (Cross-Origin Resource Sharing) 요청 핸들링하기

서버로 날라온 preflight 요청을 처리하여 웹 브라우저에서 실제 요청을 날릴 수 있도록 해준다.

모든 외부 도메인에서 모든 요청을 허용할 경우 처리

가장 쉬운 방법으로 모든 요청을 허용하는 방식이다.
1. preflight 요청을 받기 위해 OPTIONS 메서드의 요청을 받아서 컨트롤 해야 한다.
2. 모든 요청의 응답에 아래 header를 추가 한다.
  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
  • Access-Control-Max-Age: 3600
  • Access-Control-Allow-Headers: Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization

웹 브라우저의 스크립트 엔진에서 preflight 요청 응답으로 Access-Control-Allow-Origin header에 “*" 값이 있으면 모든 도메인에서의 요청을 허용하는 것으로 판단한다.  ajax 요청이 실패하면서 발생하는 메시지는 바로 preflight요청을  날린 응답 메시지에 Access-Control-Allow-Origin 해더가 없어서 요청이 허용되지 않는 다는 뜻이다.
spring-mvc 로 개발할시 처리법: https://spring.io/guides/gs/rest-service-cors/

외부 도메인 요청을 선별적으로 허용할 경우

먼저 cros 스팩과 관련된  header의 규격을 확인해보자.

Request headers (클라이언트의 요청 해더)

    • Origin: 요청을 보내는 페이지의 출처(도메인)
    • Access-Control-Request-Method: 실제 요청하려는 메서드
    • Access-Control-Request-Headers: 실제 요청에 포함되어 있는  해더 이름

    Response headers (서버에서의 응답 해더)

      • Access-Control-Allow-Origin: 요청을 허용하는 출처. * 이면 모든곳에 공개되어 있음을 의미한다.
      • Access-Control-Allow-Credentials: 클라이언트 요청이 쿠키를 통해서 자격 증명을 해야 하는 경우에 true. true를 응답 받은 클라이언트는 실제요청시 서버에서 정의된 규격의 인증값이 담긴 쿠키를 같이 보내야 한다.
      • Access-Control-Expose-Headers: 클라이언트 요청에 포함되어도 되는 사용자 정의 해더.
      • Access-Control-Max-Age: 클라이언트에서 preflight 의 요청 결과를 저장할 기간을 지정. 클라이언트에서 preflight 요청의 결과를 저장하고 있을 시간이다. 해당 시간동안은 preflight요청을 다시 하지 않게 된다.
      • Access-Control-Allow-Methods: 요청을 허용하는 메서드. 기본값은 GET,POST 라고 보면된다. 이 해더가 없으면 GET과 POST요청만 가능하다. 만약 이해더가 지정이 되어 있으면, 클라이언트에서는 해더 값에 해당하는 메서드일 경우에만 실제 요청을 시도하게 된다.
      • Access-Control-Allow-Headers: 요청을 허용하는 해더.

      위의 request header 값을 보고 response header에 해당 출처(origin)에 허용하는 요청 스팩을 알려주는 구현을 하면 된다. Filter나 Interceptor등을 통해 구현해야 한다. 그리고 자바의 경우 springframework에 CORS supprt 기능이 4.2 이상 버전부터 지원된다. spring mvc 스팩에 추가적인 어노테이션으로 외부 도메인 접속에 대한 핸들링을 지원한다.

      사용 샘플



      결론

      server side

      위에서 언급한것 처럼 이 정책을 회피하기 위한 다양한 방법이 있으므로 서버가 CORS요청을 허용하지 않는다고 해서 다른 보안정책을 마련하지 않으면 안된다. 일반적인 웹 브라우저에서 스크립트에 의한 Ajax 요청만 적용을 받을 수 있다고 생각해야 할 것이다. 그럼에도 불구하고 불특정 다수의 외부 클라이언트에서 요청을 받을 수 있는 open API 같은 것을 개발 중이라면 클라이어트가 각종 편법들을 동원해서 서버에 접근하지 않아도 되도록 cors요청을 핸들링해줄 필요가 있다.

      client side

      ajax요청시 에러가 날때 구글링을 통해서 단편적인 처방으로 문제를 해결하려고 하는 경우를 종종 봤는데 이번 기회에 javascript의 스팩을 이해하고 개발상황에 맞는 적절한 해결 방식을 선택할 수 있기를 바란다. 외부 서버로 ajax요청이 안될 경우 아래와 같은 단계로 처리를 생각해 볼 수 있다.

      1. 개발자가 테스트 혹은 개발단계에서 쉽계 요청하기: 웹 브라우저 실행옵션이나 플러그인을 통한 동일출저 정책 회피
      2. cors구현이 안되어 있는 서버로 ajax요청을 해야하지만 서버쪽 컨트롤이 불가능할 경우: jsonp방식으로 요청
      3. ajax요청을 해야하는 다른 도메인의 서버를  클라이언트와 같이 개발하거나 서버개발쪽 수정요청이 가능한 경우: 서버에서 CORS 요청이 허용되도록 구현


      참고할 페이지 링크


        10  Comments,   0  Trackbacks
        • 박옥길
          글 정말 잘 읽었습니다.
          다만 제가 모르는 부분이 있어서 혹시 조언을 얻을 수 있을까 싶어 댓글 남깁니다.
          제 웹 페이지에서 외부 사이트(조선일보, 경향신문 등...)를 띄워주고 현재 url 주소를 동적으로 얻는 기술을 구현하고 싶습니다.
          제가 url을 얻고싶어 하는 서버를 조작할 수 없으니... url을 도대체 어떻게 얻어와야 하는지 답답합니다...
          ajax말고 다른 방법이 있을지 조언 부탁드립니다 ㅠㅠ
        • 을지휘소
          좋은글 잘 읽었습니다~ 며칠 고민하던 운제에 대한 해법을 얻었네요
        • 을지휘소
          크롬에서는 플러그인이 있는데 익스플로러에서도 그런 플러그인이 있나요?
          • moonko
            익스플로러에서는 기본 제공 기능 중에
            인터넷옵션 - 보안 설정 - 도메인 간의 데이터 원본 액세스
            사용을 하면 됩니다.
        • 아킨토스
          두시간동안 삽질하다가 큰 도움 받았습니다
          정말 감사합니다 ㅠㅠ
        • 처음에 OPTIONS로 allow를 물어보는걸 모르면 큰 삽질을 하게되죠.
          CORS관련 처리에 대해서 명확하게 설명되어있네요.
          좋은 포스팅 잘 보고갑니다.
        • Welin
          깔끔하게 잘 정리된 글이네요 :) 이해가 팍팍 됩니다! 감사합니다 :)
        • yum
          저도 문제때문에 끙끙거리다가 이 글보고 해결했어요 고맙습니다.ㅎ
        • 방법이 찾아본 것 중에 이해하기 쉽고 가장 잘 정리되어있네요. 감사합니다!
        댓글 쓰기