JavaScript

SOP 문제와 CORS 처리

  • -
반응형

SOP 문제와 처리

AJAX를 생각없이 사용하다 보면 가장 많이 접하는 오류 중 하나가 SOP 오류이다.

Access to XMLHttpRequest at 'http://openapi.molit.go.kr/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTradeDev?_wadl&type=xml&serviceKey=서비스키&LAWD_CD=11110&DEAL_YMD=201512' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

 

막상 공공 API라고 해서 사용하려고 했는데 안된다면 대략 난감이다. 이런 문제는 보안상 이유로 서버에서 OpenAPI를 만들면서 자바스크립트를 이용한 직접 접근을 차단한 것이기 때문에 뭐라 할 수도 없다.

이번 포스트에서는 이 오류의 의미가 무엇인지, 어떻게 대처해야 하는지 살펴보자.

 

SOP(Same Origin Policy: 동일 근원 정책)

자바스크립트는 웹에서 심각한 보안상의 문제를 발생시키곤 한다. 그래서 다른 사이트에서 자바 스크립트에서 접근하지 못하도록 막아 놓았다. 즉 동일 근원(Same Origin)에서만 접근이 가능하게 하는 정책이다.

위 그럼에서 보듯이 동일 도메인에서 ajax 호출은 전혀 제한이 없다. 하지만 별도의 도메인에 ajax 요청을 하게 되면 SOP 위반에 의해 오류를 발생시킨다.

여기서 동일 도메인이란 프로토콜과 호스트명(ip), 포트가 같다는 것을 의미한다.

 

진행 과정

SOP를 처리하기 위해 브라우저는 요청 URL이 다른 도메인일 경우 본 요청 전에 preflight라는 사전 요청을 보내 호출 가능한 권한이 있는지 확인한다. 이를 위해 OPTIONS 메서드로 본 요청과 동일한 경로로 요청을 날려본다. 이 과정에서 권한이 없으면 본 요청을 날리지 않고 오류를 뿜어내게 된다.

2의 요청이 잘 처리된다면 3의 응답에 Access-Control-Allow-Origin 속성에 서버의 리소스를 사용할 수 있는 origin 정보가 넘어오는데 여기에 요청 도메인이 있다면 본 요청을 할 수 있고 없다면 오류가 발생하게 된다.

 

처리 방법 - 비추

CORS 문제에 대한 처리 방법은 여러가지가 있는데 먼저 비추 방법을 살펴보자. 이를 먼저 다루는 이유는 무책임한 블러그들에서 이 방법을 소개하고 생각없는 개발자들이 따라하기 때문에 이렇게 하지 말라는 취지로 먼저 정리한다.

 

브라우저에서 옵션 처리

앞서 설명했듯이 SOP를 확인하기 위해 브라우저 차원에서 preflight를 날리는데 브라우저의 실행 옵션에서 이 과정을 생략할 수 있다. 그러면 당연히 위 그림의 2, 3 과정이 생략되고 바로 본 요청을 진행하게 된다.

크롬의 경우 --disable-web-security 옵션을 주고 실행하면 사용이 가능하다. 하지만 오류가 나는 고객에게 브라우저 실행 옵션을 변경하라는건 말이 안된다.

 

크롬 플러그인 설치

크롬의 플러그인을 이용하면 위 그림의 3 부분처럼 응답 헤더에 임의로 Accsss-Control-Allow-Origin:*을 추가해서 요청 가능한 것 처럼 만들어줄 수 있다.

https://goodteacher.tistory.com/461

 

[javascript]cors 크롬 플러그인 사용

ajax를 사용하는 과정에서 CORS를 처리하기 위한 근본 대책은 아니지만 아직 backend가 구현되지 않았거나 테스트를 위한 용도라면 브라우저에 플러그인을 설치하고 사용할 수 있다. Allow CORS: Access-C

goodteacher.tistory.com

 

하지만 브라우저의 실행 옵션을 조절하는 것과 마찬가지로 고객에게 특정 플러그인을 설치하라는 것도 말이 안된다. 

 

JSONP 방식으로 요청하기

JSONP(JSON Padding) 방식은 <script> 태그를 이용해서 자바스크립트 파일을 요청하는 형태로 변경해서 처리한다.

 

이렇게 처리하면 XHR을 이용하는 것은 아니므로 SOP의 영향을 받지 않는다.  이 방법 역시 일종의 회피이고 치명적으로 get 방식밖에 지원하지 않는다는 단점이 존재한다.

 

좀 더 근본적인 대책

좀 더 근본적인 대책을 새워보자. SOP에 의해 안되는건 javascript를 이용한 호출이 안되는것이다. 즉 HTTP로 하는 요청은 전혀 문제가 없다.

 

호출 방식의 변경

SOP에 대응하기 위한 기본적인 개념은 내 서버에서 다른 서버로의 요청은 HTTP를 이용하고 AJax가 필요한 곳에서는 다시 내 서버로 요청하는 것이다.  그럼 요청이 2번 오가는 것 처럼 보이지만 ajax도 어차피 preflight가 별도로 있기 때문에 동일하다.

 

그럼 HTTP 호출하는 녀석을 또 만들어야겠네.

그럼 HTTP 호출하는 녀석을 또 만들어서 ajax와 연결해주면 되는데 단순히 데이터만 넘겨주는 녀석을 만들기 번거롭다. 이런때는 ajax-cross-origin 처럼 요청을 대신 날려주는 script library를 사용해보자.

www.ajax-cross-origin.com/

 

Ajax Cross Origin - jQuery plugin

Source code In order maintain this site and keep it running, we ask for symbolic donation before you download the sources. You can donate as much as you want, even $1 is enough. The package contains the source code files include instructions and a test pag

www.ajax-cross-origin.com

 

이 라이브러리는 ajax 요청이 발생하면 가로채서 http 요청으로 만든 후 전달해준다. 따라서 스크립트만 설치하면 추가로 할 일은 거의 없다. CDN 방식은 제공하지 않으므로 download 해서 사용한다.

다음은 아파트매매 상세 자료 조회를 위해 openapi를 사용하는 예이다.

스크립트를 로딩한 후 일반적인 ajax 코드를 사용하고 추가로 필요한 옵션은 단지 crossOrigin: true 뿐이다.

<!-- 스크립트 로딩-->
<script type="text/javascript" src="/front_end_참조/js/jquery.ajax-cross-origin.min.js"></script>
<script>
	$.ajax({
		url : "http://openapi.molit.go.kr/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTradeDev?_wadl&type=xml",
		data : {
			serviceKey : "서비스키",
			LAWD_CD : "11110",
			DEAL_YMD : "201512"
		},
		// 추가로 필요한 것은 단지 이것 하나!!
		crossOrigin : true,
		success : function(data) {
			console.log(data);
		}, error:function(){
			console.log("error");
		}
	});

그럼 나머지 처리는 ajax-cross-origin 라이브러리가 담당한다.

 

스프링 서버에서 CORS 요청 처리

이제까지는 공공 API를 제공하는 서버가 자바스크립트로부터의 직접 접근을 막았을 때 어떻게 처리할 것인가에 대한 대책을 살펴보았다. 

만약 우리가 이런 공공 API 형태의 서비스를 제공한다면 어떻게 해야할까? 

먼저 보안상 이슈로 막아버리고 싶다면 별 처리를 하지 않으면 된다. 물로 서비스를 사용하는 쪽의 입장에서는 별도의 작업이 필요할 것이다.

또는 믿을 만한 도메인의 경우 접속을 허용해줄 수 있다.

 

Controller에서의 처리

Controller에서 CORS 처리를 위해서는 @CrossOrigin 을 사용한다.

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
	@AliasFor("origins")
	String[] value() default {};
	String[] origins() default {};
}

@CrossOrigin은 Method나 클래스 레벨에 사용할 수 있는 애너테이션으로 접속을 허용할 origin 목록을 설정해주면 된다.

// 클래스 레벨 선언: 여기 선언된 모든 mapping에 적용된다.
@CrossOrigin(origins = {"http://192.168.0.1:8080", "http://192.168.0.2:8080" }) 
@RestController
public class CorssampleApplication {
 
	// 이 메서드에만 한적적으로 적용한다.
    @CrossOrigin(origins = {"*" })
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

 

WebMvcConfigurer 이용

웹과 관련된 설정을 담당하는 WebMvcConfigurer 구현체의 addCrosMappings 메서드를 사용하면 중앙 집중적으로 관련 설정을 처리할 수 있다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 모든 요청에 대해 사이트의 접속을 허용한다.
        registry.addMapping("/**")
                .allowedOrigins("http://192.168.0.1:8080","http://192.168.0.2:8080");

    }
}

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.