본문 바로가기

컴퓨터공학

CORS 정책에 의한 예외 처리하기

문제상황

Vue.js로 구현된 클라이언트에서 axios를 이용해 서버에 HTTP 요청을 전송했다. 그러나 원하는 데이터는 전송되지 않고, 아래와 같은 에러가 쓰로잉되었다.

 

Error Log

nickname-editor:1 Access to XMLHttpRequest at 'http://localhost:8080/userinfo/nickname/user1' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

에러 로그를 해석해보면, CORS 정책에 의해 HTTP 요청이 블락처리 되었음을 볼 수 있다. 또는 블락처리의 근거는 HTTP 요청 리소스에 Acces-Control-Allow-Origin 헤더가 존재하지 않는다는 것이다.

 

CORS 정책

 

CORS란? (Cross-Origin Resource Sharing)

CORS는 네트워크 통신에서 지켜야 하는 규범과 같은 것이다. 한국어로 직역하면 교차-출처 리소스 공유 정도로 해석할 수 있다. 다시 말해, 다른 출처에서도 리소스를 공유할 수 있도록 허용해주는 정책인 것이다. 여기서 출처란 무엇일까?

 

출처(Origin)?

the concept of an "origin", which is often used as the scope of authority or privilege by user agents.
RFC6454 : abstract

User agents interact with content created by a large number of authors. Although many of those authors are well-meaning, some authors might be malicious. To the extent that user agents undertake actions based on content they process, user agent implementors might wish to restrict the ability of malicious authors to disrupt the confidentiality or integrity of other content or servers.
As an example, consider an HTTP user agent that renders HTML content retrieved from various servers. If the user agent executes scripts contained in those documents, the user agent implementor might wish to prevent scripts retrieved from a malicious server from reading documents stored on an honest server, which might, for example, be behind a firewall.
Traditionally, user agents have divided content according to its "origin". More specifically, user agents allow content retrieved from one origin to interact freely with other content retrieved from that origin, but user agents restrict how that content can interact with content from another origin.

RFC6464 : introduction

출처란 User agent의 권한의 범위를 의미한다고 볼 수 있다.

 

출처의 필요성은 일부 악의적인 클라이언트에 의해 발생한다. 대부분의 소프트웨어는 클라이언트와 서버의 소통이 빈번하게 일어난다. 대부분의 클라이언트들은 정상적으로 서버와 소통하지만 일부 악의적인 클라이언트에 의해 서버의 데이터가 유실되거나 악의적으로 변형될 수 있다. 그렇기때문에 전통적인 소프트웨어 들은 서버의 컨텐츠(content)를 출처(origin)라는 이름의 일종의 식별자로 구분한 후 같은 '출처'의 컨텐츠만 다른 컨텐츠를 요청할 수 있도록 한 것이다. 이는 이후 설명할 동일 출처 정책(SOP)와 일맥상 통한다.

 

추가로 설명하자면 SOP(Same-Origin Policy)는 같은 출처끼리만 통신이 가능하게 하는 정책이고, CORS 정책은 허용된 다른 출처까지도 통신이 가능하게 하는 정책이다.

 

 

출처의 구분

If the two origins are scheme/host/port triples, the two origins are the same if, and only if, they have identical schemes, hosts, and ports.

RFC6454 : Comparing Origins

출처는 URI를 통해 표현되는데, scheme, host, port로 구성되어있다. 그리고 이 3가지 요소가 모두 같을 경우에만 동일한 출처라고 이야기할 수 있다.

위와 같은 URI가 있다고 해보자. 이때 scheme, host, port는 다음과 같이 구분된다.

  • Scheme : http
  • Host : example.co.kr
  • Port: 3030

아래 예시를 통해 쉽게 이해해보자.

    서버와의 출처 동일 여부 이유
서버 http://www.example.com:3030    
클1 http://www.example.com:3030/user/2 O  
클2 https://www.example.com:3030/user X scheme이 다름
클3 http://www.example2.com:3030 X host가 다름
클4 http://www.exmaple.com:81/user/10 X  port가 다름

 

SOP (Same-Origin Policy)

SOP는 동일 출처 정책으로 말 그대로 동일 출처의 리소스에 대한 요청만을 허용하는 것이다. 당연히 다른 악의적은 클라이언트의 요청을 거부함으로써, 소프트웨어의 무결성과 보안성을 크게 향상시킬 수 있지만, 다양한 출처로부터 리소스를 요청하고, 주고 받는 현대의 소프트웨어에 적용하기에는 한계점들이 많다.

 

그렇다면, 정반대로 모든 출처에서 리소스를 사용하도록 허용하면 어떨까? 당연히 보안의 문제를 직면하게 된다. 그래서 이에 대한 절충안으로 허용된 출처만 리소스에 접근하도록 하는 것이다. 이것이 바로 CORS 정책이다.

 

CORS 정책은 본인 출처 뿐만 아니라 개발자가 직접 허용한 출처만을 접근 허용함으로써 보안에 취약점도 개선하는 동시에 소프트웨어의 유연성을 더욱 향상시킨다.

 

 

CORS 정책의 메커니즘

For Ajax and HTTP request methods that can modify data (usually HTTP methods other than GET, or for POST usage with certain MIME types), the specification mandates that browsers "preflight" the request, soliciting supported methods from the server with an HTTP OPTIONS request method, and then, upon "approval" from the server, sending the actual request with the actual HTTP request method. Servers can also notify clients whether "credentials" (including Cookies and HTTP Authentication data) should be sent with requests.

wikipedia : Cross-origin resource sharing

위키피디아에 설명된 CORS의 메커니즘을 보면, 크게 2과정으로 나눠져 있는 것을 볼 수 있다. 일단 preflight라 불리는, HTTP OPTIONS 요청을 서버로 보낸다. 그후 서버로부터 '승인(approval)'요청이 전송되면, 그제서야 본 HTTP 요청을 보낸다.

다이어그램으로 보면 아래와 같다.

 

그렇다면 서버가 '승인' 하는 것은 어떻게 알 수 있을까? 서버는 만약 클라이언트의 출처가 허용된 출처일 경우, 다음 두 리스폰스중 1개를 보내게 된다.

첫번째 리스폰은 해당 URI가 접근하는 것이 허용된다는 것을 의미하고, 두번째 리스폰은 모든 URI에서 접근하는 것이 허용된다는 것을 의미한다.

 

만약 위와 같이 Access-Control-Allow-Origin 헤더가 서버로 부터 오지 않는다면, 이는 서버가 접근을 불허했다는 의미이고 에러를 던지게 된다. 이제 다시 위에 봤던 에러 로그를 봐보자.

nickname-editor:1 Access to XMLHttpRequest at 'http://localhost:8080/userinfo/nickname/user1' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

로그를 보면 Access-Control-Allow-Origin 헤더가 존재하지 않는다고 되어있다. 이는 곧 서버가 CORS 정책에 의해 클라이언트의 요청을 거부했음을 의미함을 알 수 있다.

 

문제해결 (with Spring REST API)

 

결국, 이 문제를 해결하기 위해서는 클라이언트의 출처에서 서버에 접근이 가능하도록 허용해주는 것이다. 현재 프로젝트를 Spring+JAVA 환경에서 구현되었으므로, 이 환경에서의 해결법을 알아보자.

 

Spring에서 서버의 출처외의 다른 출처를 허용하는 것은 매우 간단한데 @CrossOrigin 애노테이션을 허용할 요청 메서드에 붙여주면 된다.

@CrossOrigin(origin = "http://www.client.com")
@GetMapping("/items/{id}")  
public EntityModel<ItemDto> one(@PathVariable Long id) {  
    Item findItem = itemRepository.findOne(id);  
    return itemAssembler.toModel(findItem);  
}

다음과 같이 @CrossOrigin을 붙여준 후, origin을 허용할 클라이언트의 출처를 설정해주면 된다.

만약, 컨트롤러의 모든 요청 메서드를 허용해주려면 컨트롤러 자체에 애노테이션을 붙여줘도 동일하게 작동한다.

@RestController  
@CrossOrigin(origins = "http://localhost:5173")  
public class ItemController {
    ...
}

다음과 같이 컨트롤러에 애노테이션을 붙여줌녀 해당 컨트롤러의 모든 요청메서드에 적용된다.

'컴퓨터공학' 카테고리의 다른 글

Cascade에 대하여...  (0) 2022.11.22
[Network] Application 계층 - Socket Programming  (0) 2022.09.28
프록시 패턴 (Proxy Pattern)  (0) 2022.08.10
TCP에 대하여  (0) 2022.01.06