보안: ASP.NET Web API 교차-원본 요청(Cross-Origin Request) 활성화하기

등록일시: 2014-02-10 08:00,  수정일시: 2014-02-09 20:11
조회수: 16,924
이 문서는 ASP.NET Web API 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

본 자습서에서는 ASP.NET Web API에서 교차-원본 자원 공유(CORS, Cross-Origin Resource Sharing)를 지원하는 방법을 살펴봅니다.

개요

브라우저는 보안을 위해서 특정 웹 페이지에서 다른 도메인으로 AJAX 요청을 전송하는 것을 금지하는데, 참고로 이 제약사항을 동일 원본 정책(Same-Origin Policy)이라고 합니다. 그러나, 때로는 다른 사이트에서 여러분의 Web API를 호출할 수 있도록 허용하고 싶은 경우도 있습니다.

교차-원본 자원 공유(CORS, Cross-Origin Resource Sharing)는 W3C의 표준으로, 서버가 동일 원본 정책을 유연하게 우회할 수 있게 해줍니다. 즉, CORS를 사용하면 서버에서 명시적으로 지정한 일부 교차-원본 요청만 허용해주고 다른 요청들은 거부할 수 있습니다.

본문에서는 ASP.NET Web API의 새로운 CORS 지원을 살펴보도록 하겠습니다. 먼저, 두 가지 ASP.NET 프로젝트를 생성해볼텐데, 그 중 하나는 Web API 컨트롤러를 호스트할 "WebService"라는 프로젝트고, 다른 하나는 WebService를 호출할 "WebClient"라는 프로젝트입니다. 이 두 응용 프로그램은 서로 다른 도메인에서 호스트 될 것이므로, WebClient에서 WebService로 AJAX 요청을 전송하면 교차-원본 요청이 될 것입니다.

전제조건

Visual Studio 2013 Preview 또는 Visual Studio Express 2013 Preview for Web

Web API 프로젝트 생성하기

먼저, Visual Studio를 실행합니다. 그리고, 시작(Start) 페이지나 파일(File) 메뉴에서 새 프로젝트(New Project)를 클릭합니다.

그런 다음, 새 프로젝트(New Project) 대화 상자가 나타나면, 좌측 패인에서 웹(Web) 노드를 클릭한 다음, 중앙 패인에서 ASP.NET 웹 응용 프로그램(ASP.NET Web Application)을 클릭합니다. 프로젝트의 이름은 "WebService"로 지정합니다.

새 ASP.NET 프로젝트(New ASP.NET Project) 대화 상자가 나타나면 Empty 프로젝트 템플릿을 선택합니다. 그리고, "다음의 폴더 및 코어 참조 추가(Add folders and core references for)" 항목 중 Web API 체크 박스를 선택합니다. 확인(Create Project) 버튼을 클릭합니다.

Web API 컨트롤러 추가하기

이번에는 솔루션 탐색기에서 Controllers 폴더를 마우스 오른쪽 버튼으로 클릭합니다. 그리고, 추가(Add)를 선택한 다음, 스캐폴드 항목 새로 만들기(Scaffold)를 선택합니다.

스캐폴드 추가(Add Scaffold) 대화 상자가 나타나면 "Web API 2 컨트롤러 - 비어있음(Web API 2 Controller - Empty)"을 선택합니다. (이 옵션을 보려면 밑으로 스크롤해야 할 수도 있습니다.)

컨트롤러 추가(Add Controller) 대화 상자에서 컨트롤러의 이름을 "TestController"로 지정한 다음, 추가(Add) 버튼을 클릭합니다.

그리고, 자동으로 생성된 컨트롤러의 코드를 다음 코드로 변경합니다:

using System.Net.Http;
using System.Web.Http;

namespace WebService.Controllers
{
    public class TestController : ApiController
    {
        public HttpResponseMessage Get()
        {
            return new HttpResponseMessage()
            {
                Content = new StringContent("GET: Test message")
            };
        }

        public HttpResponseMessage Post()
        {
            return new HttpResponseMessage()
            {
                Content = new StringContent("POST: Test message")
            };
        }

        public HttpResponseMessage Put()
        {
            return new HttpResponseMessage()
            {
                Content = new StringContent("PUT: Test message")
            };
        }
    }
}

이제 이 응용 프로그램을 서버나 가상 머신(VM)에 배포합니다. (본 자습서의 스크린샷들은 Windows Azure 웹 사이트에 배포된 응용 프로그램을 캡쳐한 것입니다.)

Web API가 정상적으로 동작하는지 확인해보려면 http://hostname/api/test/를 방문해보면 되는데, 이 URI에서 hostname 부분에는 응용 프로그램을 배포한 도메인을 지정하면 됩니다. 정상적인 경우라면 "GET: Test Message"라는 응답 텍스트가 반환될 것입니다.

클라이언트 응용 프로그램 생성하기

다시 솔루션 탐색기에서 솔루션을 마우스 오른쪽 버튼으로 클릭하고 추가(Add)를 선택한 다음, 새 프로젝트(New Project)를 선택합니다.

새 프로젝트 추가(Add New Project) 대화 상자가 나타나면, 이전처럼 ASP.NET 웹 응용 프로그램(ASP.NET Web Application)을 선택합니다. 이번에는 프로젝트의 이름을 "WebClient"로 지정하고 확인(OK) 버튼을 클릭합니다.

계속해서 새 ASP.NET 프로젝트(New ASP.NET Project) 대화 상자가 나타나면 MVC 프로젝트 템플릿을 선택합니다. 필요에 따라, 인증 변경(Change Authentication) 버튼을 클릭한 다음, 인증 안 함(No Authentication) 옵션을 선택합니다. 확인(Create Project) 버튼을 클릭합니다.

솔루션 탐색기에서 WebClient 프로젝트를 확장하고 Views/Home/Index.cshtml 파일을 엽니다. 그리고, 이 파일의 코드를 다음 코드로 대체합니다:

<div>
    <select id="method">
        <option value="get">GET</option>
        <option value="post">POST</option>
        <option value="put">PUT</option>
    </select>
    <input type="button" value="Try it" onclick="sendRequest()" />
    <span id='value1'>(Result)</span>
</div>

@@section scripts {
<script>
    var serviceUrl = 'http://myservice.azurewebsites.net/api/test'; // Replace with your URI.

    function sendRequest() {
        var method = $('#method').val();

        $.ajax({
            type: method,
            url: serviceUrl
        }).done(function (data) {
            $('#value1').text(data);
        }).error(function (jqXHR, textStatus, errorThrown) {
            $('#value1').text(jqXHR.responseText || textStatus);
        });
    }
</script>
}

이 코드에서 serviceUrl 변수에는 앞에서 배포한 WebService 응용 프로그램의 URI를 지정하면 됩니다.

이제 WebClient 프로젝트를 WebService 응용 프로그램과 다른 도메인에 배포합니다.

테스트를 위해서 WebClient 응용 프로그램에 접근해서 "Try It" 버튼을 클릭해보면, 드롭다운 박스에 선택된 HTTP 메서드 중 하나를 (GET, POST, 또는 PUT) 이용해서, 이 페이지로부터 WebService 응용 프로그램으로 AJAX 요청이 제출됩니다. 이 기능을 활용하면 다양한 유형의 CORS 요청을 직접 살펴볼 수 있습니다. 그러나 지금은 WebService 응용 프로그램이 교차-원본 요청을 지원하지 않으므로, 버튼을 클릭해보면 오류가 발생할 것입니다.

피들러나 F12 개발자 도구를 이용해서 HTTP 트래픽을 직접 살펴보면, 브라우저가 정상적으로 GET 요청을 전송하는 것을 확인할 수 있습니다. 그러나, 정작 응용 프로그램에서는 이 AJAX 호출 시 오류가 발생합니다. 동일-원본 정책은 브라우저가 요청 전송을 금지하는 것이 아니라는 점을 이해해야만 합니다. 동일-원본 정책은 교차-도메인 응용 프로그램의 응답 수신을 금지합니다.

Web API에서 CORS 활성화시키기

Web API에서 CORS를 활성화시키려면, NuGet을 통해서 설치할 수 있는 Microsoft.AspNet.WebApi.Cors 패키지를 이용해야 합니다.

이번에는 Visual Studio의 도구(Tools) 메뉴에서 라이브러리 패키지 관리자(Library Package Manager)를 선택한 다음, 패키지 관리자 콘솔(Package Manager Console)을 선택합니다. 그리고, 패키지 관리자 콘솔 창에 다음 명령을 입력합니다:

Install-Package Microsoft.AspNet.WebApi.Cors -pre -project WebService

이 명령은 필요한 모든 종속성과 함께 CORS 패키지를 WebService 프로젝트에 설치해줍니다.

계속해서 솔루션 탐색기에서 WebService 프로젝트를 확장하고, App_Start/WebApiConfig.cs 파일을 엽니다. WebApiConfig.Register 메서드에 다음 코드를 추가합니다.

using System.Web.Http;
namespace WebService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // New code
            config.EnableCors();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

그런 다음, 다음과 같이 [EnableCors] 어트리뷰트를 TestController 클래스에 추가합니다:

using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;

namespace WebService.Controllers
{
    [EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*", methods: "*")]
    public class TestController : ApiController
    {
        // Controller methods not shown...
    }
}

이 코드에서 origins 매개변수에는 WebClient 응용 프로그램이 배포된 URI를 지정합니다. 이 작업들을 마치고 나면 WebClient로부터의 교차-도메인 요청이 허용됩니다. 잠시 후에, [EnableCors] 어트리뷰트의 매개변수들을 보다 자세히 살펴보도록 하겠습니다.

변경된 WebService 응용 프로그램을 배포합니다. 이번에는 WebClient의 AJAX 요청이 성공할 것입니다:

POST 및 PUT 메서드 역시 허용됩니다:

범위별 [EnableCors] 어트리뷰트 적용 규칙

CORS은 액션 범위를 대상으로, 컨트롤러 범위를 대상으로, 또는 응용 프로그램에 존재하는 모든 Web API 컨트롤러에 전역으로 활성화시킬 수 있습니다.

액션 범위

단일 액션을 대상으로 CORS을 활성화시키려면, 해당 액션 메서드에 [EnableCors] 어트리뷰트를 설정합니다. 다음 예제에서는 GetItem 메서드만 CORS을 활성화시킵니다.

public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }

    [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
    public HttpResponseMessage GetItem(int id) { ... }

    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage PutItem(int id) { ... }
}

컨트롤러 범위

[EnableCors] 어트리뷰트를 컨트롤러 클래스 자체에 설정하면, 컨트롤러에 존재하는 모든 액션 메서드들에 적용됩니다. 그런 다음, 특정 액션만 CORS을 비활성화시키려면 해당 액션에 [DisableCors] 어트리뷰트를 설정합니다. 다음 예제에서는 PutItem을 제외한 모든 컨트롤러 메서드들의 CORS을 활성화시킵니다.

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }
    public HttpResponseMessage GetItem(int id) { ... }
    public HttpResponseMessage Post() { ... }

    [DisableCors]
    public HttpResponseMessage PutItem(int id) { ... }
}

전역

응용 프로그램의 모든 Web API 컨트롤러들을 대상으로 CORS을 활성화시키려면, EnableCors 메서드에 EnableCorsAttribute의 인스턴스를 전달합니다:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute("www.example.com", "*", "*");
        config.EnableCors(cors);
        // ...
    }
}

만약, 한 가지 이상의 범위에 중복해서 어트리뷰트를 적용한 경우, 그 우선순위는 다음과 같습니다:

  1. 액션
  2. 컨트롤러
  3. 전역

동작 원리

이번 절에서는 HTTP 메시지 수준에서 CORS 요청이 실제로 어떻게 수행되는지 살펴보도록 하겠습니다. [EnableCors] 어트리뷰트를 올바르게 구성하고, CORS이 정상적으로 동작하지 않을 때 문제를 해결하려면 CORS의 동작 원리를 이해하는 것이 중요합니다.

CORS 명세서에서는 교차-원본 요청을 활성화시키기 위해 몇 가지 새로운 HTTP 헤더들이 도입되었습니다. 다만, 브라우저가 CORS을 지원하는 경우, 브라우저가 교차-원본 요청 시 자동으로 이 헤더들을 설정해주므로 자바스크립트 코드 등으로 직접 처리해줘야 할 작업은 전혀 없습니다.

다음은 교차-원본 요청의 실제 예제입니다:

GET http://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: http://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: http://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net

브라우저는 요청을 생성한 사이트의 도메인이 지정된 "Origin" 헤더를 자동으로 추가해줍니다. 그러면, 서버는 해당 요청을 허용하는 경우에 한해 Access-Control-Allow-Origin 헤더를 지정해서 응답을 반환합니다. 이 Access-Control-Allow-Origin 헤더의 값은 Origin 헤더 값과 같거나, 모든 원본이 허용됨을 뜻하는 와일드카드 값인 "*" 이어야 합니다.

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 05 Jun 2013 06:27:30 GMT
Content-Length: 17

GET: Test message

만약, 응답에 Access-Control-Allow-Origin 헤더가 포함되어 있지 않다면 해당 AJAX 요청은 실패하게 됩니다. 조금 더 구체적으로 말해서 브라우저가 요청을 허용하지 않는 것입니다. 설령 서버가 정상적인 응답을 반환하더라도, 브라우저가 그 응답을 클라이언트 응용 프로그램에서 사용할 수 있도록 허용하지 않습니다.

예비 요청(Preflight Requests)

브라우저는 일부 CORS 요청에 대해서, 리소스에 대한 실제 요청을 전송하기 전에, "예비 요청(Preflight Requests)"이라고 불리는 별도의 요청을 전송합니다.

다음과 같은 조건들을 만족하는 경우, 브라우저는 예비 요청을 생략할 수 있습니다:

  • 요청 메서드가 GET, HEAD, 또는 POST 이고,
  • 응용 프로그램이 Accept, Accept-Language, Content-Language, Content-Type, 및 Last-Event-ID 이외의 다른 요청 헤더를 지정하지 않고,
  • Content-Type 헤더가 (지정된 경우) 다음 중 하나인 경우:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

이 예비 요청의 헤더 관련 규칙들은 XMLHttpRequest 개체의 setRequestHeader 메서드를 호출해서 응용 프로그램이 설정한 헤더들을 대상으로만 적용됩니다. (CORS 명세서에서는 이런 헤더들을 "사용자 지정 요청 헤더(Author Request Headers)"라고 부릅니다.) 이 규칙은 User-Agent, Host, 또는 Content-Length 같이 브라우저에 의해서 지정된 헤더들에는 적용되지 않습니다.

다음은 예비 요청의 실제 예제입니다:

OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: http://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0

예비 요청에는 HTTP OPTIONS 메서드가 사용되는데, 이 메서드에는 두 가지 특별한 헤더가 존재합니다:

  • Access-Control-Request-Method: 실제 요청에서 사용될 HTTP 메서드가 지정됩니다.
  • Access-Control-Request-Headers: 실제 요청에서 응용 프로그램이 설정할 요청 헤더들의 목록입니다.

다음은 서버가 이 요청을 허용한다고 가정한 경우의 예제 응답입니다:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 05 Jun 2013 06:33:22 GMT

이 응답에는 허가된 메서드들의 목록이 담겨 있는 Access-Control-Allow-Methods 헤더가 포함되어 있습니다. 그리고, 선택적으로 허가된 헤더들의 목록이 지정된 Access-Control-Allow-Headers 헤더도 포함되어 있을 수 있습니다. 이 예비 요청이 성공한 경우, 브라우저는 이전에 살펴봤던 것과 같은 실제 요청을 전송합니다.

접근을 허용할 원본 지정하기

[EnableCors] 어트리뷰트의 origins 매개변수에는 리소스에 접근을 허용할 도메인들을 지정합니다. 그 값은 콤마로 연결된 허가될 도메인들의 목록입니다.

[EnableCors(origins: "http://www.contoso.com,http://www.example.com", headers: "*", methods: "*")]

모든 도메인의 요청을 허용하기 위해 와일드카드 값인 "*" 를 사용할 수도 있습니다.

모든 원본의 요청을 허용하기 전에 주의 깊게 생각해보시기 바랍니다. 이는 문자 그대로 모든 웹 페이지가 Web API에 AJAX 요청을 보낼 수 있다는 뜻입니다.

// Allow CORS for all origins. (Caution!)
[EnableCors(origins: "*", headers: "*", methods: "*")]

접근을 허용할 HTTP 메서드 지정하기

[EnableCors] 어트리뷰트의 methods 매개변수에는 리소스에 접근을 허용할 HTTP 메서드들을 지정합니다. 모든 메서드들을 허용하려면 와일드카드 값인 "*" 를 사용합니다. 다음 예제에서는 GET 및 POST 요청만 허용하고 있습니다.

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
public class TestController : ApiController
{
    public HttpResponseMessage Get() { ... }
    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage Put() { ... }    
}

접근을 허용할 헤더 지정하기

요청 헤더

이미 앞에서 응용 프로그램에 의해 설정되는 HTTP 헤더들(사용자 지정 요청 헤더)의 목록이 지정되는 Access-Control-Request-Headers가 예비 요청에 포함되는 방식을 살펴봤습니다. [EnableCors] 어트리뷰트의 headers 매개변수에는 리소스에 접근을 허용할 사용자 지정 요청 헤더들을 지정합니다. 모든 헤더를 허용하려면 와일드카드 값인 "*" 를 사용합니다. 헤더들의 화이트 리스트 목록을 지정하려면 허용될 헤더들의 목록을 콤마로 연결해서 지정합니다:

[EnableCors(origins: "http://example.com", headers: "accept,content-type,origin,x-my-header", methods: "*")]

그러나, 브라우저들이 Access-Control-Request-Headers를 설정하는 방법이 전적으로 똑같지는 않습니다. 가령, Chrome은 현재 "origin"을 포함시키는 반면, FireFox는 "Accept" 같은 표준 헤더들을 응용 프로그램에서 스크립트로 설정하는 경우에도 포함시키지 않습니다.

만약, headers 매개변수에 "*" 이 아닌 다른 값을 설정하는 경우, 최소한 "accept", "content-type", 그리고 "origin"을 포함시켜야 하고, 그에 더해서 지원하고자 하는 모든 사용자 지정 헤더들을 포함시키면 됩니다.

응답 헤더

기본적으로 브라우저는 응용 프로그램에 모든 응답 헤더를 노출하지 않습니다. 기본적으로 사용 가능한 응답 헤더들은 다음과 같습니다:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

CORS 명세서에서는 이 헤더들을 단순 응답 헤더(Simple Response Header)라고 부릅니다. 응용 프로그램에서 다른 헤더들을 사용할 수 있으려면 [EnableCors] 어트리뷰트의 exposedHeaders 매개변수를 설정해야 합니다.

다음 예제에서는 컨트롤러의 Get 메서드에 'X-Custom-Header'라는 이름의 사용자 지정 헤더를 설정하고 있습니다. 기본적으로 브라우저는 교차-원본 요청에 이 헤더를 노출하지 않으므로, 이 헤더를 사용하려면 이 예제 코드에서처럼 exposedHeaders 매개변수에 'X-Custom-Header'를 포함시켜야 합니다.

[EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
public class TestController : ApiController
{
    public HttpResponseMessage Get()
    {
        var resp = new HttpResponseMessage()
        {
            Content = new StringContent("GET: Test message")
        };
        resp.Headers.Add("X-Custom-Header", "hello");
        return resp;
    }
}

다음은 서버의 응답 예제입니다:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Expires: -1
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-Custom-Header: hello
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: X-Custom-Header
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 07 Jun 2013 18:55:39 GMT
Content-Length: 17

GET: Test message

자격 증명

CORS 요청 시, 자격 증명(Credentials)은 특별한 처리를 필요로 합니다. 기본적으로 브라우저는 교차-원본 요청 시에 어떠한 자격 증명도 전송하지 않습니다. (여기에서 말하는 자격 증명에는 쿠키뿐만 아니라 HTTP 인증도 포함됩니다.) 교차-원본 요청에 자격 증명을 함께 전송하려면 XMLHttpRequest 개체의 withCredentials 속성을 true로 설정합니다.

XMLHttpRequest 개체를 직접 사용하는 경우:

var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;

jQuery를 사용하는 경우:

$.ajax({
    type: 'get',
    url: 'http://www.example.com/api/test',
    xhrFields: {
        withCredentials: true
    }

그리고, 서버에서도 자격 증명을 허용해야만 합니다. 서버, 즉 Web API에서 교차-원본 자격 증명을 허용하려면 [EnableCors] 어트리뷰트의 SupportsCredentials 속성을 true로 설정합니다:

[EnableCors(origins: "http://myclient.azurewebsites.net",
            headers: "*", methods: "*", SupportsCredentials = true)]

이 속성이 true로 설정되면 HTTP 응답에 Access-Control-Allow-Credentials 헤더가 포함됩니다. 이 헤더는 브라우저에게 서버가 교차-원본 요청에 대한 자격 증명을 허용한다는 사실을 말해줍니다.

브라우저가 자격 증명을 전송했지만 응답에 유효한 Access-Control-Allow-Credentials 헤더가 포함되어 있지 않다면, 브라우저가 응용 프로그램에게 응답을 노출하지 않으므로 AJAX 요청은 실패하게 됩니다.

그리고, 사용자가 전혀 의식하지 못하는 사이에, 다른 도메인의 웹사이트가 사용자 대신 로그인 된 사용자의 자격 증명을 Web API에 전송할 수도 있으므로, SupportsCredentials 속성을 true로 설정할 것인지는 대단히 심사숙고해서 결정해야만 합니다. 또한, CORS 명세서에 따라 SupportsCredentials 속성이 true로 설정된 경우, 원본을 "*" 로 설정하는 것은 유효하지 않다는 점도 기억해두시기 바랍니다.

사용자 지정 정책 공급자

지금까지 살펴본 [EnableCors] 어트리뷰트는 ICorsPolicyProvider 인터페이스를 구현하고 있습니다. 그러므로, 필요한 경우 Attribute 클래스를 상속받고 ICorsProlicyProvider 인터페이스를 구현한 클래스를 작성해서 직접 사용자 지정 정책 공급자를 구현할 수도 있습니다.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider 
{
    private CorsPolicy _policy;

    public MyCorsPolicyAttribute()
    {
        // Create a CORS policy.
        _policy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        // Add allowed origins.
        _policy.Origins.Add("http://myclient.azurewebsites.net");
        _policy.Origins.Add("http://www.contoso.com");
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
    {
        return Task.FromResult(_policy);
    }
}

그런 다음, [EnableCors] 어트리뷰트를 지정할 수 있는 모든 위치에 이 어트리뷰트를 대신 지정할 수 있습니다.

[MyCorsPolicy]
public class TestController : ApiController
{
    .. // 

이런 사용자 지정 CORS 정책 공급자를 이용해서 구성 파일에서 설정을 읽어서 동작하도록 하는 등의 활용이 가능합니다.

또는 어트리뷰트를 사용하는 대신, ICorsPolicyProvider 개체를 생성하는 ICorsPolicyProviderFactory 개체를 등록할 수도 있습니다.

public class CorsPolicyFactory : ICorsPolicyProviderFactory
{
    ICorsPolicyProvider _provider = new MyCorsPolicyProvider();

    public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
    {
        return _provider;
    }
} 

ICorsPolicyProviderFactory를 설정하려면, 구동 시에 다음과 같이 SetCorsPolicyProviderFactory 확장 메서드를 호출하면 됩니다:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
        config.EnableCors();

        // ...
    }
}

요약

본문에서 살펴본 CORS을 사용하면, JSONP 같은 기존의 기법들보다 안전하고 훨씬 유연한 방법으로, 교차-원본 요청을 허용할 수 있습니다. 더불어 Microsoft.AspNet.WebApi.Cors 라이브러리를 이용하면, 간단하게 [EnableCors] 어트리뷰트를 컨트롤러에 적용하는 것만으로도 Web API 응용 프로그램에서 CORS을 지원할 수 있습니다. 더욱 더 효과적인 제어가 필요한 경우에는 사용자 지정 CORS 정책 공급자를 구현할 수도 있습니다.