Learn business/Network

CORS 이슈 해결


CORS 이슈가 발생한 상황을 예제 코드를 통해서 확인하고 서버 사이드에서 해당 이슈를 어떻게 해결하는지 확인해도록 하자.

 

이전 포스팅은 CORS에 대한 개념에 대한 전반적인 내용 설명이었다면, 이번엔 예제 코드를 통해서 눈으로 직접 확인해보자. 참고로 웹 브라우저는 Firefox를 사용하였다. 그 이유는 Chrome에서는 기본적으로 Access-Control-Max-Age의 최소 값을 10분(600초)으로 설정해놔서 테스트를 하면서 계속 기다려야했기 때문이다. Chrome에서 Access-Control-Max-Age를 설정한 이유는 이전 포스팅에서도 언급하였지만, Preflight Request는 많은 리소스를 소비하기 때문이다.

 

예제 테스트는 UI서버API서버를 포트를 다르게 로컬에서 띄운 상태에서 UI서버에서 Cross-Origin(포트만 다르게하여)으로 <script>태그 내에 ajax 요청을 하였다. 

 

CORS - Simple Request 방식 예제

위 코드는 UI서버(http://localhost:8088)에서 API서버(http://localhost:8080)으로 ajax요청을 한다. HTTP Method가 GET 방식이며 ContentType이 application/x-www-form-urlencoded이므로 브라우저는 Simple Request 방식으로 Cross-Origin으로 요청을 보낸다.

 

그리고 API 코드는 아래와 같다. Spring Framework 기반으로 서버 코드를 작성하였으며, Filter를 상속받아 재구현하여 web.xml에 필터를 등록해 주었다.

위 Filter에서 CORS와 관련된 어떠한 헤더 값도 넣어주지 않았을 때 웹 브라우저는 Cross-Origin으로 요청한 리소스에 접근할 수 없다는 에러 메시지를 콘솔에 찍는다.

Simple Request CORS issue

 

CORS에 대해 공부를 하면서 다른 포스팅을 보다가 몇몇 글들에서 틀린 내용을 적어 논 것이 있었다. "웹 브라우저가 요청을 해도 되는지 판단해서 외부 서버로 요청한다 것", "웹 브라우저가 요청이 가능한 사이트로 인식한다라는 것" 이라는 문장이었는데, 이 내용은 정확히 틀린 말이다. 브라우저는 Cross-Origin인 서버에 요청이 가능한지 불가능한지 알 수 없다. 오로지 Cross-Origin으로부터 응답 받은 헤더를 통해 해당 리소스에 접근할 수 있는지 없는지를 판단하는 것 뿐이다. 무슨 수로 웹 브라우저가 외부 서버에 요청할 수 있고 없고를 판단할 수 있단 말인가? 그 증거로 Simple Request 방식으로 CORS 허용을 안한 상태에서 디버그를 찍어보면 요청이 Cross-Origin 서버로 들어왔다는 것을 확인할 수 있다.

디버그 모드로 서버를 띄우고 요청이 오는지 안오는지 확인

 

위 사진처럼 Intellij에서 디버그 모드로 서버를 구동하고 요청이 오는지 안오는지 테스트를 해봤는데 요청이 들어오는 것을 확인할 수 있다. 이전 포스팅에서도 말했지만, 스크립트 내에 Cross-Origin 요청이더라도 결국 웹 브라우저는 외부 서버에 요청하게 되고 서버로부터 받아온 응답 헤더를 통해서 그 리소스에 접근할 수 있는지 없는지를 브라우저가 판단하는 것 뿐이다. 

 

Simple Request 방식은 단순히 서버에서 응답 헤더에 Access-Control-Allow-Origin의 값만 세팅해주면 해결된다. CorsFilter.java 클래스에서 응답 헤더에 요청 받은 Origin 값을 세팅해주면 아래와 같다.

 

그리고 웹 브라우저에서 응답헤더를 보면 위 코드에서 설정한 값이 헤더에 담아져서 넘어온 것을 확인할 수 있다.

CORS :: Simple Reqeust

 

CORS - Preflight Request 방식 예제

 Preflight Request 방식 OPTIONS 방식으로 먼저 요청을 날리고, 그 이후에 실제 요청을 한다.

 

HTTP Method가 POST 방식이며 ContentType이 application/json 이므로 브라우저는 Preflight Request 방식으로 Cross-Origin 서버에 요청을 보낸다. 그리고 API 코드는 아래와 같다.

 

CorsFilter 클래스를 보면 Origin은 설정해 주었지만, "Access-Control-Allow-Headers: content-type"의 헤더를 설정해주지 않았기 때문에 브라우저는 아래와 같은 에러를 뱉는다.  또한 웹 브라우저는 Preflight Request 방식으로 HTTP Method OPTIONS으로 먼저 확인을 하며, 응답 헤더에 "Access-Control-Allow-Headers: content-type" 내용이 없기 때문에 실제 요청은 보내지 않는 것을 확인할 수 있다.

preflight request
content-type

응답 헤더에 "Access-Control-Allow-Headers: content-type"의 값을 넣어서 테스트 해보면 정상적으로 Preflight Request 방식으로 브라우저가 리소스에 접근하는 모습을 볼 수 있다.

Preflight Request 방식

 

추가적으로 CORS의 성능 효율을 위해서 Access-Control-Max-Age를 설정한다고 하였는데 단위는 초이며, 첫 요청에 HTTP Method인 OPTIONS으로 요청하고 캐싱하여 그 다음 요청부터는 실제 요청만 보낼 수 있도록 한다. 그리고 설정된 시간 뒤에 다시 OPTION 메소드로 요청한다. 한번 Access-Control-Max-Age: 10으로 값을 설정한 뒤에 브라우저의 네트워크 탭을 확인하면 10초 뒤에 다시 OPTIONS 요청을 보내는 것을 확인할 수 있다. 

10초뒤 다시  OPTIONS 메소드 호출