ASP.NET Core에서 CORS(원본 간 요청) 사용

작성자: Rick AndersonKirk Larkin

이 문서에서는 ASP.NET Core 앱에서 CORS를 사용하도록 설정하는 방법을 보여 줍니다.

브라우저 보안 때문에 웹 페이지에서 해당 웹 페이지를 제공한 도메인이 아닌 다른 도메인에 요청을 수행할 수는 없습니다. 이러한 제한 사항을 동일 원본 정책이라고 합니다. 동일 원본 정책은 악성 사이트에서 다른 사이트의 중요한 데이터를 읽지 못하도록 차단합니다. 경우에 따라 다른 사이트에서 앱에 대한 원본 간 요청을 수행할 수 있도록 허용하는 것이 좋습니다. 자세한 내용은 Mozilla CORS 문서를 참조하세요.

CORS(원본 간 리소스 공유):

  • 서버에서 동일 원본 정책을 완화할 수 있게 해 주는 W3C 표준입니다.
  • 보안 기능이 아니며, CORS는 보안을 완화합니다. CORS를 허용하여 API가 더 안전해지지 않게 됩니다. 자세한 내용은 CORS 작동 방식을 참조하세요.
  • 서버가 일부 원본 간 요청만 명시적으로 허용하고 다른 요청은 거부하도록 허용합니다.
  • ONP와 같은 JS이전 기술보다 더 안전하고 유연합니다.

예제 코드 살펴보기 및 다운로드 (다운로드 방법)

동일 원본

두 URL은 동일한 체계, 호스트 및 포트(RFC 6454)가 있는 경우 동일 원본을 갖습니다.

다음 두 URL은 동일 원본을 갖습니다.

  • https://example.com/foo.html
  • https://example.com/bar.html

이러한 URL은 이전 두 URL과는 다른 원본을 갖습니다.

  • https://example.net: 다른 도메인
  • https://www.example.com/foo.html: 다른 하위 도메인
  • http://example.com/foo.html: 다른 체계
  • https://example.com:9000/foo.html: 다른 포트

CORS를 사용하도록 설정

CORS를 사용하도록 설정하는 방법에는 다음 세 가지가 있습니다.

명명된 정책과 함께 [EnableCors] 특성을 사용하면 CORS를 지원하는 엔드포인트를 제한할 수 있습니다.

경고

UseCors는 올바른 순서로 호출되어야 합니다. 자세한 내용은 미들웨어 순서를 참조하세요. 예를 들어 UseResponseCaching을 사용할 때는 UseCorsUseResponseCaching 이전에 호출해야 합니다.

각 접근 방식은 다음 섹션에 자세히 설명되어 있습니다.

명명된 정책 및 미들웨어를 사용하는 CORS

CORS 미들웨어는 원본 간 요청을 처리합니다. 다음 코드는 지정된 원본을 사용하는 모든 앱의 엔드포인트에 CORS 정책을 적용합니다.

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

위의 코드는

  • 정책 이름을 _myAllowSpecificOrigins로 설정합니다. 정책 이름은 임의로 지정할 수 있습니다.
  • 확장 메서드를 UseCors 호출하고 CORS 정책을 지정합니다 _myAllowSpecificOrigins . UseCors는 CORS 미들웨어를 추가합니다. UseCors에 대한 호출은 UseRouting 다음, UseAuthorization 앞에 배치해야 합니다. 자세한 내용은 미들웨어 순서를 참조하세요.
  • 람다 식을 통해 AddCors를 호출합니다. 람다는 CorsPolicyBuilder 개체를 가져옵니다. WithOrigins와 같은 구성 옵션은 이 문서의 뒷부분에서 설명합니다.
  • 모든 컨트롤러 엔드포인트에 대해 _myAllowSpecificOrigins CORS 정책을 사용하도록 설정합니다. 특정 엔드포인트에 CORS 정책을 적용하려면 엔드포인트 라우팅을 참조하세요.
  • 응답 캐싱 미들웨어를 사용하는 경우 UseResponseCaching 전에 UseCors를 호출합니다.

엔드포인트 라우팅을 사용하면 UseRoutingUseEndpoints 호출 사이에 CORS 미들웨어를 실행하도록 구성해야 합니다.

이전 코드와 유사한 코드 테스트에 대한 지침은 CORS 테스트를 참조하세요.

AddCors 메서드 호출은 앱의 서비스 컨테이너에 CORS 서비스를 추가합니다.

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

자세한 내용은 이 문서의 CORS 정책 옵션을 참조하세요.

CorsPolicyBuilder 메서드는 다음 코드와 같이 연결될 수 있습니다.

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

참고: 지정된 URL은 후행 슬래시(/)를 포함하면 안됩니다. URL이 /로 끝나면 비교가 false를 반환하고 헤더는 반환되지 않습니다.

UseCors 및 UseStaticFiles 순서

일반적으로는 UseStaticFiles 앞에 UseCors호출됩니다. JavaScript를 사용하여 사이트 간 정적 파일을 검색하는 앱은 먼저 UseStaticFiles호출 UseCors 해야 합니다.

기본 정책 및 미들웨어를 사용하는 CORS

다음 강조 표시된 코드는 기본 CORS 정책을 사용하도록 설정합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

위의 코드는 모든 컨트롤러 엔드포인트에 기본 CORS 정책을 적용합니다.

엔드포인트 라우팅을 사용하여 Cors 사용

엔드포인트별로 CORS를 사용하도록 RequireCors 설정해도 자동 실행 전 요청은 지원되지 않습니다. 자세한 내용은 이 GitHub 문제엔드포인트 라우팅 및 [HttpOptions]를 사용하여 CORS 테스트를 참조하세요.

엔드포인트 라우팅을 사용하면 확장 메서드의 RequireCors 집합을 사용하여 엔드포인트별로 CORS를 사용하도록 설정할 수 있습니다.

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

위의 코드에서

  • app.UseCors는 CORS 미들웨어를 사용하도록 설정합니다. 기본 정책이 구성되지 않았기 때문에 app.UseCors()만으로 CORS를 사용하도록 설정할 수는 없습니다.
  • /echo 및 컨트롤러 엔드포인트는 지정된 정책을 사용하여 원본 간 요청을 허용합니다.
  • 기본 정책이 지정되지 않았으므로 /echo2 및 Razor Pages 엔드포인트는 원본 간 요청을 허용하지 않습니다.

[DisableCors] 특성은 .을 사용하여 엔드포인트 라우팅에 의해 활성화된 CORS를 RequireCors사용하지 않도록 설정하지 않습니다.

이전과 유사한 코드 테스트에 대한 지침은 엔드포인트 라우팅 및 [HttpOptions]를 통해 CORS 테스트를 참조하세요.

특성을 통해 CORS 사용

[EnableCors] 특성을 통해 CORS를 사용하도록 설정하고 CORS가 필요한 엔드포인트에만 명명된 정책을 적용하면 최상의 제어가 구현됩니다.

[EnableCors] 특성은 CORS를 전역적으로 적용하는 대안을 제공합니다. [EnableCors] 특성은 모든 엔드포인트가 아닌 선택한 엔드포인트에 대해서만 CORS를 사용하도록 설정합니다.

  • [EnableCors]는 기본 정책을 지정합니다.
  • [EnableCors("{Policy String}")]은 명명된 정책을 지정합니다.

[EnableCors] 특성은 다음에 적용될 수 있습니다.

  • Razor Page PageModel
  • 컨트롤러
  • 컨트롤러 작업 메서드

[EnableCors] 특성을 사용하여 컨트롤러, 페이지 모델 또는 작업 메서드에 다양한 정책을 적용할 수 있습니다. [EnableCors] 특성이 컨트롤러, 페이지 모델 또는 작업 메서드에 적용되고 CORS가 미들웨어에서 사용하도록 설정되면 두 정책이 모두 적용됩니다. 정책을 결합하지 않도록 하는 것이 좋습니다. 동일한 앱에서[EnableCors]둘 다 아닌 특성 또는 미들웨어를 사용합니다.

다음 코드는 각 메서드에 다른 정책을 적용합니다.

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

다음 코드는 두 개의 CORS 정책을 만듭니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

CORS 요청 제한을 가장 세밀하게 제어하려면 다음을 수행합니다.

  • 명명된 정책과 함께 [EnableCors("MyPolicy")]를 사용합니다.
  • 기본 정책을 정의하지 않습니다.
  • 엔드포인트 라우팅을 사용하지 않습니다.

다음 섹션의 코드는 위의 목록을 충족합니다.

이전 코드와 유사한 코드 테스트에 대한 지침은 CORS 테스트를 참조하세요.

CORS 사용 안 함

[DisableCors] 특성은 엔드포인트 라우팅에서 사용하도록 설정된 CORS를 사용하지 않도록 설정하지 않습니다.

다음 코드는 CORS 정책 "MyPolicy"를 정의합니다.

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

다음 코드는 GetValues2 작업에 대해 CORS를 사용하지 않도록 설정합니다.

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

위의 코드는

이전 코드의 테스트에 대한 지침은 CORS 테스트를 참조하세요.

CORS 정책 옵션

이 섹션에서는 CORS 정책에서 설정할 수 있는 다양한 옵션에 대해 설명합니다.

AddPolicyProgram.cs에서 호출됩니다. 일부 옵션의 경우 CORS 작동 방법 섹션을 먼저 읽는 것이 유용할 수 있습니다.

허용되는 원본 설정

AllowAnyOrigin: 임의의 체계(http 또는 https)를 사용하여 모든 원본에서 CORS 요청을 허용합니다. ‘모든 웹 사이트’에서 앱에 대해 원본 간 요청을 수행할 수 있기 때문에 AllowAnyOrigin은 안전하지 않습니다.

참고

AllowAnyOriginAllowCredentials를 지정하는 것은 안전하지 않은 구성이며 사이트 간 요청이 위조될 수 있습니다. 앱이 두 가지 방법으로 구성된 경우 CORS 서비스는 잘못된 CORS 응답을 반환합니다.

AllowAnyOrigin은 사전 실행 요청 및 Access-Control-Allow-Origin 헤더에 영향을 줍니다. 자세한 내용은 사전 실행 요청 섹션을 참조하세요.

SetIsOriginAllowedToAllowWildcardSubdomains: 정책의 IsOriginAllowed 속성을 원본이 허용되는지 평가할 때 구성된 와일드 카드 도메인과 일치하도록 허용하는 함수로 설정합니다.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

허용되는 HTTP 메서드 설정

AllowAnyMethod:

  • 다음과 같이 모든 HTTP 메서드를 허용합니다.
  • 사전 실행 요청 및 Access-Control-Allow-Methods 헤더에 영향을 줍니다. 자세한 내용은 사전 실행 요청 섹션을 참조하세요.

허용되는 요청 헤더 설정

작성자 요청 헤더라는 특정 헤더를 CORS 요청에서 보낼 수 있게 하려면 WithHeaders를 호출하고 허용되는 헤더를 지정합니다.

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

모든 작성자 요청 헤더를 허용하려면 AllowAnyHeader를 호출합니다.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader는 사전 실행 요청 및 Access-Control-Request-Headers 헤더에 영향을 줍니다. 자세한 내용은 사전 실행 요청 섹션을 참조하세요.

WithHeaders로 지정된 특정 헤더에 대한 CORS 미들웨어 정책 일치는 Access-Control-Request-Headers에 전송된 헤더가 WithHeaders에 명시된 헤더와 정확히 일치할 때만 가능합니다.

예를 들어 다음과 같이 구성된 앱을 고려합니다.

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

Content-Language(HeaderNames.ContentLanguage)가 WithHeaders에 나열되지 않으므로 CORS 미들웨어는 다음 요청 헤더가 있는 사전 실행 요청을 거부합니다.

Access-Control-Request-Headers: Cache-Control, Content-Language

앱은 200 OK 응답을 반환하지만 CORS 헤더를 다시 전송하지 않습니다. 따라서 브라우저는 원본 간 요청을 시도하지 않습니다.

노출된 응답 헤더 설정

기본적으로 브라우저는 앱에 모든 응답 헤더를 노출하지는 않습니다. 자세한 내용은 W3C 원본 간 리소스 공유(용어): 단순 응답 헤더를 참조하세요.

기본적으로 사용할 수 있는 응답 헤더는 다음과 같습니다.

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

CORS 사양은 이러한 헤더를 ‘단순 응답 헤더’라고 합니다. 앱에서 다른 헤더를 사용할 수 있도록 하려면 WithExposedHeaders를 호출합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

원본 간 요청의 자격 증명

자격 증명은 CORS 요청에서 특수하게 처리해야 합니다. 기본적으로 브라우저는 원본 간 요청과 함께 자격 증명을 보내지 않습니다. 자격 증명에는 cookie 및 HTTP 인증 체계가 포함됩니다. 원본 간 요청을 사용하여 자격 증명을 보내려면 클라이언트는 XMLHttpRequest.withCredentialstrue로 설정해야 합니다.

XMLHttpRequest 직접 사용:

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

jQuery 사용:

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

Fetch API 사용:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

서버에서 자격 증명을 허용해야 합니다. 원본 간 자격 증명을 허용하려면 AllowCredentials를 호출합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

HTTP 응답에는 서버가 원본 간 요청에 대해 자격 증명을 허용한다는 사실을 브라우저에 알리는 Access-Control-Allow-Credentials 헤더가 포함되어 있습니다.

브라우저에서 자격 증명을 전송하지만 응답에 유효한 Access-Control-Allow-Credentials 헤더가 포함되지 않은 경우 브라우저는 앱에 응답을 노출하지 않으며 원본 간 요청이 실패합니다.

원본 간 자격 증명을 허용하는 것은 보안상 위험합니다. 다른 도메인의 웹 사이트는 사용자가 모르는 사이에 사용자를 대신하여 로그인한 사용자의 자격 증명을 앱에 보낼 수 있습니다.

또한 CORS 사양은 Access-Control-Allow-Credentials 헤더가 있는 경우 원본을 "*"(모든 원본)으로 설정할 수 없다고 명시합니다.

사전 실행 요청

일부 CORS 요청의 경우 브라우저는 실제 요청을 수행하기 전에 추가 OPTIONS 요청을 보냅니다. 이 요청을 사전 실행 요청이라고 합니다. 다음 조건을 모두 만족할 경우 브라우저에서 사전 실행 요청을 건너뛸 수 있습니다.

  • 요청 메서드가 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 사양은 이러한 헤더를 작성자 요청 헤더라고 지칭합니다. 이 규칙은 브라우저에서 설정할 수 있는 헤더(예: User-Agent, Host 또는 Content-Length)에는 적용되지 않습니다.

다음은 이 문서의 CORS 테스트 섹션에서 [Put test] 단추를 통해 수행된 사전 실행 요청과 유사한 응답의 예입니다.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

사전 실행 요청은 HTTP OPTIONS 메서드를 사용합니다. 여기에는 다음 헤더가 포함될 수 있습니다.

사전 실행 요청이 거부되면 앱은 200 OK 응답을 반환하지만 CORS 헤더를 설정하지 않습니다. 따라서 브라우저는 원본 간 요청을 시도하지 않습니다. 거부된 사전 실행 요청에 대한 예는 이 문서의 CORS 테스트 섹션을 참조하세요.

F12 도구를 사용하면 콘솔 앱은 브라우저에 따라 다음 중 하나와 유사한 오류를 표시합니다.

  • Firefox: 원본 간 요청이 차단됨: 동일 원본 정책이 https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5에서 원격 리소스를 읽는 것을 허용하지 않습니다. (이유: CORS 요청이 성공하지 못함). 자세한 내용
  • Chromium 기반: 원본 ‘ https://cors3.azurewebsites.net ’에서 ‘ https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5 ’에 가져오기 위한 액세스 권한이 CORS 정책에 의해 차단됨: 사전 실행 요청에 대한 응답이 액세스 제어 검사를 통과하지 못함: 요청한 리소스에 ‘Access-Control-Allow-Origin’ 헤더가 없음 사용자 요구에 불명확한 응답이 제공되면 요청 모드를 'no-cors'로 설정하여 CORS가 사용되지 않도록 설정된 리소스를 가져옵니다.

특정 헤더를 허용하려면 WithHeaders를 호출합니다.

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

모든 작성자 요청 헤더를 허용하려면 AllowAnyHeader를 호출합니다.

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

브라우저의 Access-Control-Request-Headers 설정 방법이 일관되지 않습니다. 다음 중 하나일 경우:

  • 헤더는 "*" 이외의 값으로 설정됩니다.
  • AllowAnyHeader가 호출됨: 지원하려는 사용자 지정 헤더 외에 Accept, Content-TypeOrigin 이상을 포함합니다.

자동 사전 실행 요청 코드

다음 두 가지 방식 중 하나로 CORS 정책이 적용되는 경우:

  • Program.cs에서 app.UseCors를 호출하여 전역적으로
  • [EnableCors] 특성을 사용하여

ASP.NET Core는 사전 실행 OPTIONS 요청에 응답합니다.

RequireCors를 사용하여 엔드포인트별로 CORS를 사용하도록 설정하면 자동 사전 실행 요청이 지원되지 않습니다.

이 문서의 CORS 테스트 섹션에서 이 동작을 보여 줍니다.

사전 실행 요청의 [HttpOptions] 특성

적절한 정책으로 CORS를 사용하도록 설정하면 ASP.NET Core는 일반적으로 CORS 사전 실행 요청에 자동으로 응답합니다. 일부 시나리오에서는 이러한 경우가 해당되지 않을 수 있습니다. 엔드포인트 라우팅에서 CORS를 사용하는 경우가 여기에 해당합니다.

다음 코드는 [HttpOptions] 특성을 사용하여 OPTIONS 요청에 대한 엔드포인트를 만듭니다.

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

이전 코드의 테스트에 대한 지침은 엔드포인트 라우팅 및 [HttpOptions]를 통해 CORS 테스트를 참조하세요.

사전 실행 만료 시간 설정

Access-Control-Max-Age 헤더는 사전 실행 요청에 대한 응답을 캐시할 수 있는 기간을 지정합니다. 이 헤더를 설정하려면 SetPreflightMaxAge를 호출합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

CORS 작동 방식

이 섹션에서는 HTTP 메시지 수준에서 CORS 요청에서 발생하는 작업을 설명합니다.

  • CORS는 보안 기능이 아닙니다. CORS는 서버에서 동일 원본 정책을 완화할 수 있게 해 주는 W3C 표준입니다.
    • 예를 들어 악의적인 행위자가 사이트에 대해 XSS(교차 사이트 스크립팅)를 사용하고 CORS 지원 사이트에 대한 교차 사이트 요청을 실행하여 정보를 도용할 수 있습니다.
  • CORS를 허용하여 API가 더 안전해지지 않게 됩니다.
    • CORS 적용 여부는 클라이언트(브라우저)에 달려 있습니다. 서버는 요청을 실행하고 응답을 반환하며, 오류를 반환하고 응답을 차단하는 것은 클라이언트입니다. 예를 들어 다음 도구는 서버 응답을 표시합니다.
  • 서버에서 브라우저가 원본 간 XHR 또는 Fetch API 요청을 실행할 수 있도록 허용하는 방법으로, 원래는 사용할 수 없는 방법입니다.
    • CORS가 없는 브라우저는 원본 간 요청을 수행할 수 없습니다. CORS JS이전에는 ONP 를 사용하여 이 제한을 피했습니다. JSONP는 XHR을 사용하지 않고 태그를 <script> 사용하여 응답을 받습니다. 스크립트는 원본 간에 로드할 수 있습니다.

CORS 사양에서는 원본 간 요청을 사용하도록 설정하는 몇 가지 새로운 HTTP 헤더를 도입했습니다. 브라우저에서 CORS를 지원하는 경우 원본 간 요청에 대해 이러한 헤더를 자동으로 설정합니다. 사용자 지정 JavaScript 코드는 CORS를 사용하도록 설정하는 데 필요하지 않습니다.

배포된 샘플PUT 테스트 단추

다음은 Values 테스트 단추에서 https://cors1.azurewebsites.net/api/values로의 원본 간 요청 예제입니다. Origin 헤더는 다음과 같습니다.

  • 요청을 수행하는 사이트의 도메인을 제공합니다.
  • 필수로, 호스트와 달라야 합니다.

일반 헤더

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

응답 헤더

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

요청 헤더

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

요청에서 OPTIONS 서버는 응답에서 응답 헤더 헤더Access-Control-Allow-Origin: {allowed origin} 를 설정합니다. 예를 들어 배포된 샘플삭제 [EnableCors] 단추 OPTIONS 요청에는 다음 헤더가 포함됩니다.

일반 헤더

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

응답 헤더

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

요청 헤더

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

앞의 응답 헤더에서 서버는 응답에 Access-Control-Allow-Origin 헤더를 설정합니다. 이 헤더의 https://cors1.azurewebsites.net 값은 요청의 Origin 헤더와 일치합니다.

AllowAnyOrigin이 호출되면 와일드카드 값인 Access-Control-Allow-Origin: *이 반환됩니다. AllowAnyOrigin은 모든 원본을 허용합니다.

응답에 Access-Control-Allow-Origin 헤더가 포함되지 않으면 원본 간 요청이 실패합니다. 특히 브라우저는 요청을 허용하지 않습니다. 서버가 성공적인 응답을 반환하더라도 브라우저는 클라이언트 앱에서 응답을 사용할 수 있도록 하지 않습니다.

HTTPS로의 HTTP 리디렉션은 CORS 실행 전 요청에서 ERR_INVALID_REDIRECT 발생합니다.

실패하여 HTTPS로 리디렉션되는 HTTP를 사용하여 엔드포인트에 UseHttpsRedirectionERR_INVALID_REDIRECT on the CORS preflight request대한 요청

API 프로젝트는 요청을 HTTPS로 리디렉션하는 데 사용하지 UseHttpsRedirection 않고 HTTP 요청을 거부할 수 있습니다.

OPTIONS 요청 표시

기본적으로 Chrome 및 Edge 브라우저는 F12 도구의 네트워크 탭에 OPTIONS 요청을 표시하지 않습니다. 이러한 브라우저에서 OPTIONS 요청을 표시하려면 다음을 수행합니다.

  • chrome://flags/#out-of-blink-cors 또는 edge://flags/#out-of-blink-cors
  • 플래그를 사용하지 않도록 설정합니다.
  • 다시 시작합니다.

Firefox는 기본적으로 OPTIONS 요청을 표시합니다.

IIS의 CORS

IIS에 배포할 때 서버가 익명 액세스를 허용하도록 구성되지 않은 경우 Windows 인증 전에 CORS를 실행해야 합니다. 이 시나리오를 지원하려면 앱에 대해 IIS CORS 모듈을 설치하고 구성해야 합니다.

CORS 테스트

샘플 다운로드에는 CORS를 테스트하는 코드가 있습니다. 다운로드하는 방법을 참조하세요. 샘플은 Razor Pages가 추가된 API 프로젝트입니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

경고

WithOrigins("https://localhost:<port>");샘플 코드 다운로드와 유사한 샘플 앱을 테스트하는 데만 사용해야 합니다.

다음 ValuesController는 테스트용 엔드포인트를 제공합니다.

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfoRick.Docs.Samples.RouteInfo NuGet 패키지가 제공하며 경로 정보를 표시합니다.

다음 방법 중 하나를 사용하여 위의 샘플 코드를 테스트합니다.

  • https://cors3.azurewebsites.net/에서 배포된 샘플 앱을 사용합니다. 샘플을 다운로드할 필요가 없습니다.
  • https://localhost:5001의 기본 URL을 사용하여 dotnet run에서 샘플을 실행합니다.
  • https://localhost:44398의 URL에 대해 포트가 44398로 설정된 샘플을 Visual Studio에서 실행합니다.

F12 도구와 함께 브라우저 사용:

  • 단추를 선택하고 네트워크 탭에서 헤더를 검토합니다.

  • PUT 테스트 단추를 선택합니다. OPTIONS 요청을 표시하기 위한 지침에 대해서는 OPTIONS 요청 표시를 참조하세요. PUT 테스트는 두 개의 요청, 즉 OPTIONS 사전 실행 요청과 PUT 요청을 만듭니다.

  • GetValues2 [DisableCors] 단추를 선택하여 실패한 CORS 요청을 트리거합니다. 이 문서에 설명된 대로 응답은 200 성공을 반환하지만 CORS 요청은 수행되지 않습니다. 콘솔 탭을 선택하여 CORS 오류를 확인합니다. 브라우저에 따라 다음과 유사한 오류가 표시됩니다.

    원본 'https://cors3.azurewebsites.net'에서 'https://cors1.azurewebsites.net/api/values/GetValues2'에 가져오기 위한 액세스 권한이 CORS 정책에 의해 차단됨: 요청된 리소스에 ‘Access-Control-Allow-Origin’ 헤더가 없음 사용자 요구에 불명확한 응답이 제공되면 요청 모드를 'no-cors'로 설정하여 CORS가 사용되지 않도록 설정된 리소스를 가져옵니다.

CORS 지원 엔드포인트는 curl, Fiddler 또는 Postman과 같은 도구를 사용하여 테스트할 수 있습니다. 도구를 사용하는 경우 Origin 헤더로 지정된 요청의 원본은 요청을 수신하는 호스트와 달라야 합니다. 요청이 Origin 헤더 값에 따라 ‘원본 간’이 아닌 경우:

  • CORS 미들웨어가 요청을 처리할 필요가 없습니다.
  • CORS 헤더가 응답에 반환되지 않습니다.

다음 명령은 curl을 사용하여 정보를 포함하는 OPTIONS 요청을 실행합니다.

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

엔드포인트 라우팅 및 [HttpOptions]를 통해 CORS 테스트

RequireCors를 사용하여 엔드포인트별로 CORS를 사용하도록 설정하면 자동 사전 실행 요청이 지원되지 않습니다. 엔드포인트 라우팅을 사용하여 CORS를 사용하도록 설정하는 다음 코드를 고려합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

다음 TodoItems1Controller는 테스트용 엔드포인트를 제공합니다.

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

배포된 샘플테스트 페이지에서 이전 코드를 테스트합니다.

엔드포인트에 [EnableCors]가 있고 사전 실행 요청에 응답하므로 Delete [EnableCors]GET [EnableCors] 단추는 성공합니다. 다른 엔드포인트는 실패합니다. JavaScript에서 다음을 보내기 때문에 GET 단추가 실패합니다.

 headers: {
      "Content-Type": "x-custom-header"
 },

다음 TodoItems2Controller는 유사한 엔드포인트를 제공하지만 OPTIONS 요청에 응답하는 명시적 코드를 포함합니다.

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    [EnableCors]  // Rquired for this path
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]  // Rquired for this path
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

배포된 샘플의 테스트 페이지에서 이전 코드를 테스트합니다. 컨트롤러 드롭다운 목록에서 사전 실행을 선택한 다음, 컨트롤러를 설정합니다. TodoItems2Controller 엔드포인트에 대한 모든 CORS 호출이 성공합니다.

추가 자료

작성자: Rick AndersonKirk Larkin

이 문서에서는 ASP.NET Core 앱에서 CORS를 사용하도록 설정하는 방법을 보여 줍니다.

브라우저 보안 때문에 웹 페이지에서 해당 웹 페이지를 제공한 도메인이 아닌 다른 도메인에 요청을 수행할 수는 없습니다. 이러한 제한 사항을 동일 원본 정책이라고 합니다. 동일 원본 정책은 악성 사이트에서 다른 사이트의 중요한 데이터를 읽지 못하도록 차단합니다. 경우에 따라 다른 사이트에서 앱에 대한 원본 간 요청을 수행할 수 있도록 허용하는 것이 좋습니다. 자세한 내용은 Mozilla CORS 문서를 참조하세요.

CORS(원본 간 리소스 공유):

  • 서버에서 동일 원본 정책을 완화할 수 있게 해 주는 W3C 표준입니다.
  • 보안 기능이 아니며, CORS는 보안을 완화합니다. CORS를 허용하여 API가 더 안전해지지 않게 됩니다. 자세한 내용은 CORS 작동 방식을 참조하세요.
  • 서버가 일부 원본 간 요청만 명시적으로 허용하고 다른 요청은 거부하도록 허용합니다.
  • ONP와 같은 JS이전 기술보다 더 안전하고 유연합니다.

예제 코드 살펴보기 및 다운로드 (다운로드 방법)

동일 원본

두 URL은 동일한 체계, 호스트 및 포트(RFC 6454)가 있는 경우 동일 원본을 갖습니다.

다음 두 URL은 동일 원본을 갖습니다.

  • https://example.com/foo.html
  • https://example.com/bar.html

이러한 URL은 이전 두 URL과는 다른 원본을 갖습니다.

  • https://example.net: 다른 도메인
  • https://www.example.com/foo.html: 다른 하위 도메인
  • http://example.com/foo.html: 다른 체계
  • https://example.com:9000/foo.html: 다른 포트

CORS를 사용하도록 설정

CORS를 사용하도록 설정하는 방법에는 다음 세 가지가 있습니다.

명명된 정책과 함께 [EnableCors] 특성을 사용하면 CORS를 지원하는 엔드포인트를 제한할 수 있습니다.

경고

UseCors는 올바른 순서로 호출되어야 합니다. 자세한 내용은 미들웨어 순서를 참조하세요. 예를 들어 UseResponseCaching을 사용할 때는 UseCorsUseResponseCaching 이전에 호출해야 합니다.

각 접근 방식은 다음 섹션에 자세히 설명되어 있습니다.

명명된 정책 및 미들웨어를 사용하는 CORS

CORS 미들웨어는 원본 간 요청을 처리합니다. 다음 코드는 지정된 원본을 사용하는 모든 앱의 엔드포인트에 CORS 정책을 적용합니다.

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        // services.AddResponseCaching();
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors(MyAllowSpecificOrigins);

        // app.UseResponseCaching();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

위의 코드는

  • 정책 이름을 _myAllowSpecificOrigins로 설정합니다. 정책 이름은 임의로 지정할 수 있습니다.
  • 확장 메서드를 UseCors 호출하고 CORS 정책을 지정합니다 _myAllowSpecificOrigins . UseCors는 CORS 미들웨어를 추가합니다. UseCors에 대한 호출은 UseRouting 다음, UseAuthorization 앞에 배치해야 합니다. 자세한 내용은 미들웨어 순서를 참조하세요.
  • 람다 식을 통해 AddCors를 호출합니다. 람다는 CorsPolicyBuilder 개체를 가져옵니다. WithOrigins와 같은 구성 옵션은 이 문서의 뒷부분에서 설명합니다.
  • 모든 컨트롤러 엔드포인트에 대해 _myAllowSpecificOrigins CORS 정책을 사용하도록 설정합니다. 특정 엔드포인트에 CORS 정책을 적용하려면 엔드포인트 라우팅을 참조하세요.
  • 응답 캐싱 미들웨어를 사용하는 경우 UseResponseCaching 전에 UseCors를 호출합니다.

엔드포인트 라우팅을 사용하면 UseRoutingUseEndpoints 호출 사이에 CORS 미들웨어를 실행하도록 구성해야 합니다.

이전 코드와 유사한 코드 테스트에 대한 지침은 CORS 테스트를 참조하세요.

AddCors 메서드 호출은 앱의 서비스 컨테이너에 CORS 서비스를 추가합니다.

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        // services.AddResponseCaching();
        services.AddControllers();
    }

자세한 내용은 이 문서의 CORS 정책 옵션을 참조하세요.

CorsPolicyBuilder 메서드는 다음 코드와 같이 연결될 수 있습니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
    });

    services.AddControllers();
}

참고: 지정된 URL은 후행 슬래시(/)를 포함하면 안됩니다. URL이 /로 끝나면 비교가 false를 반환하고 헤더는 반환되지 않습니다.

기본 정책 및 미들웨어를 사용하는 CORS

다음 강조 표시된 코드는 기본 CORS 정책을 사용하도록 설정합니다.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddDefaultPolicy(
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });
        });

        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

위의 코드는 모든 컨트롤러 엔드포인트에 기본 CORS 정책을 적용합니다.

엔드포인트 라우팅을 사용하여 Cors 사용

엔드포인트별로 CORS를 사용하도록 설정해도 RequireCors자동 실행 전 요청은 지원되지 않습니다. 자세한 내용은 엔드포인트 라우팅 및 [HttpOptions]를 사용하여 이 GitHub 문제 및 테스트 CORS를 참조하세요.

엔드포인트 라우팅을 사용하면 확장 메서드의 RequireCors 집합을 사용하여 엔드포인트별로 CORS를 사용하도록 설정할 수 있습니다.

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGet("/echo",
                context => context.Response.WriteAsync("echo"))
                .RequireCors(MyAllowSpecificOrigins);

            endpoints.MapControllers()
                     .RequireCors(MyAllowSpecificOrigins);

            endpoints.MapGet("/echo2",
                context => context.Response.WriteAsync("echo2"));

            endpoints.MapRazorPages();
        });
    }
}

위의 코드에서

  • app.UseCors는 CORS 미들웨어를 사용하도록 설정합니다. 기본 정책이 구성되지 않았기 때문에 app.UseCors()만으로 CORS를 사용하도록 설정할 수는 없습니다.
  • /echo 및 컨트롤러 엔드포인트는 지정된 정책을 사용하여 원본 간 요청을 허용합니다.
  • 기본 정책이 지정되지 않았으므로 /echo2 및 Razor Pages 엔드포인트는 원본 간 요청을 허용하지 않습니다.

[DisableCors] 특성은 엔드포인트 라우팅에서 사용하도록 설정된 CORS를 RequireCors사용하지 않도록 설정하지 않습니다.

이전과 유사한 코드 테스트에 대한 지침은 엔드포인트 라우팅 및 [HttpOptions]를 통해 CORS 테스트를 참조하세요.

특성을 통해 CORS 사용

[EnableCors] 특성을 통해 CORS를 사용하도록 설정하고 CORS가 필요한 엔드포인트에만 명명된 정책을 적용하면 최상의 제어가 구현됩니다.

[EnableCors] 특성은 CORS를 전역적으로 적용하는 대안을 제공합니다. [EnableCors] 특성은 모든 엔드포인트가 아닌 선택한 엔드포인트에 대해서만 CORS를 사용하도록 설정합니다.

  • [EnableCors]는 기본 정책을 지정합니다.
  • [EnableCors("{Policy String}")]은 명명된 정책을 지정합니다.

[EnableCors] 특성은 다음에 적용될 수 있습니다.

  • Razor Page PageModel
  • 컨트롤러
  • 컨트롤러 작업 메서드

[EnableCors] 특성을 사용하여 컨트롤러, 페이지 모델 또는 작업 메서드에 다양한 정책을 적용할 수 있습니다. [EnableCors] 특성이 컨트롤러, 페이지 모델 또는 작업 메서드에 적용되고 CORS가 미들웨어에서 사용하도록 설정되면 두 정책이 모두 적용됩니다. 정책을 결합하지 않도록 하는 것이 좋습니다. 동일한 앱에서 둘 다 아닌 특성 또는 미들웨어를 사용합니다.[EnableCors]

다음 코드는 각 메서드에 다른 정책을 적용합니다.

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

다음 코드는 두 개의 CORS 정책을 만듭니다.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("Policy1",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });

            options.AddPolicy("AnotherPolicy",
                policy =>
                {
                    policy.WithOrigins("http://www.contoso.com")
                                        .AllowAnyHeader()
                                        .AllowAnyMethod();
                });
        });

        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

CORS 요청 제한을 가장 세밀하게 제어하려면 다음을 수행합니다.

  • 명명된 정책과 함께 [EnableCors("MyPolicy")]를 사용합니다.
  • 기본 정책을 정의하지 않습니다.
  • 엔드포인트 라우팅을 사용하지 않습니다.

다음 섹션의 코드는 위의 목록을 충족합니다.

이전 코드와 유사한 코드 테스트에 대한 지침은 CORS 테스트를 참조하세요.

CORS 사용 안 함

[DisableCors] 특성은 엔드포인트 라우팅에서 사용하도록 설정된 CORS를 사용하지 않도록 설정하지 않습니다.

다음 코드는 CORS 정책 "MyPolicy"를 정의합니다.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapRazorPages();
        });
    }
}

다음 코드는 GetValues2 작업에 대해 CORS를 사용하지 않도록 설정합니다.

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

위의 코드는

이전 코드의 테스트에 대한 지침은 CORS 테스트를 참조하세요.

CORS 정책 옵션

이 섹션에서는 CORS 정책에서 설정할 수 있는 다양한 옵션에 대해 설명합니다.

AddPolicyStartup.ConfigureServices에서 호출됩니다. 일부 옵션의 경우 CORS 작동 방법 섹션을 먼저 읽는 것이 유용할 수 있습니다.

허용되는 원본 설정

AllowAnyOrigin: 임의의 체계(http 또는 https)를 사용하여 모든 원본에서 CORS 요청을 허용합니다. ‘모든 웹 사이트’에서 앱에 대해 원본 간 요청을 수행할 수 있기 때문에 AllowAnyOrigin은 안전하지 않습니다.

참고

AllowAnyOriginAllowCredentials를 지정하는 것은 안전하지 않은 구성이며 사이트 간 요청이 위조될 수 있습니다. 앱이 두 가지 방법으로 구성된 경우 CORS 서비스는 잘못된 CORS 응답을 반환합니다.

AllowAnyOrigin은 사전 실행 요청 및 Access-Control-Allow-Origin 헤더에 영향을 줍니다. 자세한 내용은 사전 실행 요청 섹션을 참조하세요.

SetIsOriginAllowedToAllowWildcardSubdomains: 정책의 IsOriginAllowed 속성을 원본이 허용되는지 평가할 때 구성된 와일드 카드 도메인과 일치하도록 허용하는 함수로 설정합니다.

options.AddPolicy("MyAllowSubdomainPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
            .SetIsOriginAllowedToAllowWildcardSubdomains();
    });

허용되는 HTTP 메서드 설정

AllowAnyMethod:

  • 다음과 같이 모든 HTTP 메서드를 허용합니다.
  • 사전 실행 요청 및 Access-Control-Allow-Methods 헤더에 영향을 줍니다. 자세한 내용은 사전 실행 요청 섹션을 참조하세요.

허용되는 요청 헤더 설정

작성자 요청 헤더라는 특정 헤더를 CORS 요청에서 보낼 수 있게 하려면 WithHeaders를 호출하고 허용되는 헤더를 지정합니다.

options.AddPolicy("MyAllowHeadersPolicy",
    policy =>
    {
        // requires using Microsoft.Net.Http.Headers;
        policy.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

모든 작성자 요청 헤더를 허용하려면 AllowAnyHeader를 호출합니다.

options.AddPolicy("MyAllowAllHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

AllowAnyHeader는 사전 실행 요청 및 Access-Control-Request-Headers 헤더에 영향을 줍니다. 자세한 내용은 사전 실행 요청 섹션을 참조하세요.

WithHeaders로 지정된 특정 헤더에 대한 CORS 미들웨어 정책 일치는 Access-Control-Request-Headers에 전송된 헤더가 WithHeaders에 명시된 헤더와 정확히 일치할 때만 가능합니다.

예를 들어 다음과 같이 구성된 앱을 고려합니다.

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

Content-Language(HeaderNames.ContentLanguage)가 WithHeaders에 나열되지 않으므로 CORS 미들웨어는 다음 요청 헤더가 있는 사전 실행 요청을 거부합니다.

Access-Control-Request-Headers: Cache-Control, Content-Language

앱은 200 OK 응답을 반환하지만 CORS 헤더를 다시 전송하지 않습니다. 따라서 브라우저는 원본 간 요청을 시도하지 않습니다.

노출된 응답 헤더 설정

기본적으로 브라우저는 앱에 모든 응답 헤더를 노출하지는 않습니다. 자세한 내용은 W3C 원본 간 리소스 공유(용어): 단순 응답 헤더를 참조하세요.

기본적으로 사용할 수 있는 응답 헤더는 다음과 같습니다.

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

CORS 사양은 이러한 헤더를 ‘단순 응답 헤더’라고 합니다. 앱에서 다른 헤더를 사용할 수 있도록 하려면 WithExposedHeaders를 호출합니다.

options.AddPolicy("MyExposeResponseHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .WithExposedHeaders("x-custom-header");
    });

원본 간 요청의 자격 증명

자격 증명은 CORS 요청에서 특수하게 처리해야 합니다. 기본적으로 브라우저는 원본 간 요청과 함께 자격 증명을 보내지 않습니다. 자격 증명에는 cookie 및 HTTP 인증 체계가 포함됩니다. 원본 간 요청을 사용하여 자격 증명을 보내려면 클라이언트는 XMLHttpRequest.withCredentialstrue로 설정해야 합니다.

XMLHttpRequest 직접 사용:

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

jQuery 사용:

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

Fetch API 사용:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

서버에서 자격 증명을 허용해야 합니다. 원본 간 자격 증명을 허용하려면 AllowCredentials를 호출합니다.

options.AddPolicy("MyMyAllowCredentialsPolicy",
    policy =>
    {
        policy.WithOrigins("http://example.com")
               .AllowCredentials();
    });

HTTP 응답에는 서버가 원본 간 요청에 대해 자격 증명을 허용한다는 사실을 브라우저에 알리는 Access-Control-Allow-Credentials 헤더가 포함되어 있습니다.

브라우저에서 자격 증명을 전송하지만 응답에 유효한 Access-Control-Allow-Credentials 헤더가 포함되지 않은 경우 브라우저는 앱에 응답을 노출하지 않으며 원본 간 요청이 실패합니다.

원본 간 자격 증명을 허용하는 것은 보안상 위험합니다. 다른 도메인의 웹 사이트는 사용자가 모르는 사이에 사용자를 대신하여 로그인한 사용자의 자격 증명을 앱에 보낼 수 있습니다.

또한 CORS 사양은 Access-Control-Allow-Credentials 헤더가 있는 경우 원본을 "*"(모든 원본)으로 설정할 수 없다고 명시합니다.

사전 실행 요청

일부 CORS 요청의 경우 브라우저는 실제 요청을 수행하기 전에 추가 OPTIONS 요청을 보냅니다. 이 요청을 사전 실행 요청이라고 합니다. 다음 조건을 모두 만족할 경우 브라우저에서 사전 실행 요청을 건너뛸 수 있습니다.

  • 요청 메서드가 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 사양은 이러한 헤더를 작성자 요청 헤더라고 지칭합니다. 이 규칙은 브라우저에서 설정할 수 있는 헤더(예: User-Agent, Host 또는 Content-Length)에는 적용되지 않습니다.

다음은 이 문서의 CORS 테스트 섹션에서 [Put test] 단추를 통해 수행된 사전 실행 요청과 유사한 응답의 예입니다.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

사전 실행 요청은 HTTP OPTIONS 메서드를 사용합니다. 여기에는 다음 헤더가 포함될 수 있습니다.

사전 실행 요청이 거부되면 앱은 200 OK 응답을 반환하지만 CORS 헤더를 설정하지 않습니다. 따라서 브라우저는 원본 간 요청을 시도하지 않습니다. 거부된 사전 실행 요청에 대한 예는 이 문서의 CORS 테스트 섹션을 참조하세요.

F12 도구를 사용하면 콘솔 앱은 브라우저에 따라 다음 중 하나와 유사한 오류를 표시합니다.

  • Firefox: 원본 간 요청이 차단됨: 동일 원본 정책이 https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5에서 원격 리소스를 읽는 것을 허용하지 않습니다. (이유: CORS 요청이 성공하지 못함). 자세한 내용
  • Chromium 기반: 원본 ‘ https://cors3.azurewebsites.net ’에서 ‘ https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5 ’에 가져오기 위한 액세스 권한이 CORS 정책에 의해 차단됨: 사전 실행 요청에 대한 응답이 액세스 제어 검사를 통과하지 못함: 요청한 리소스에 ‘Access-Control-Allow-Origin’ 헤더가 없음 사용자 요구에 불명확한 응답이 제공되면 요청 모드를 'no-cors'로 설정하여 CORS가 사용되지 않도록 설정된 리소스를 가져옵니다.

특정 헤더를 허용하려면 WithHeaders를 호출합니다.

options.AddPolicy("MyAllowHeadersPolicy",
    policy =>
    {
        // requires using Microsoft.Net.Http.Headers;
        policy.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

모든 작성자 요청 헤더를 허용하려면 AllowAnyHeader를 호출합니다.

options.AddPolicy("MyAllowAllHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

브라우저의 Access-Control-Request-Headers 설정 방법이 일관되지 않습니다. 다음 중 하나일 경우:

  • 헤더는 "*" 이외의 값으로 설정됩니다.
  • AllowAnyHeader가 호출됨: 지원하려는 사용자 지정 헤더 외에 Accept, Content-TypeOrigin 이상을 포함합니다.

자동 사전 실행 요청 코드

다음 두 가지 방식 중 하나로 CORS 정책이 적용되는 경우:

  • Startup.Configure에서 app.UseCors를 호출하여 전역적으로
  • [EnableCors] 특성을 사용하여

ASP.NET Core는 사전 실행 OPTIONS 요청에 응답합니다.

RequireCors를 사용하여 엔드포인트별로 CORS를 사용하도록 설정하면 자동 사전 실행 요청이 지원되지 않습니다.

이 문서의 CORS 테스트 섹션에서 이 동작을 보여 줍니다.

사전 실행 요청의 [HttpOptions] 특성

적절한 정책으로 CORS를 사용하도록 설정하면 ASP.NET Core는 일반적으로 CORS 사전 실행 요청에 자동으로 응답합니다. 일부 시나리오에서는 이러한 경우가 해당되지 않을 수 있습니다. 엔드포인트 라우팅에서 CORS를 사용하는 경우가 여기에 해당합니다.

다음 코드는 [HttpOptions] 특성을 사용하여 OPTIONS 요청에 대한 엔드포인트를 만듭니다.

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

이전 코드의 테스트에 대한 지침은 엔드포인트 라우팅 및 [HttpOptions]를 통해 CORS 테스트를 참조하세요.

사전 실행 만료 시간 설정

Access-Control-Max-Age 헤더는 사전 실행 요청에 대한 응답을 캐시할 수 있는 기간을 지정합니다. 이 헤더를 설정하려면 SetPreflightMaxAge를 호출합니다.

options.AddPolicy("MySetPreflightExpirationPolicy",
    policy =>
    {
        policy.WithOrigins("http://example.com")
               .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
    });

CORS 작동 방식

이 섹션에서는 HTTP 메시지 수준에서 CORS 요청에서 발생하는 작업을 설명합니다.

  • CORS는 보안 기능이 아닙니다. CORS는 서버에서 동일 원본 정책을 완화할 수 있게 해 주는 W3C 표준입니다.
    • 예를 들어 악의적인 행위자가 사이트에 대해 XSS(교차 사이트 스크립팅)를 사용하고 CORS 지원 사이트에 대한 교차 사이트 요청을 실행하여 정보를 도용할 수 있습니다.
  • CORS를 허용하여 API가 더 안전해지지 않게 됩니다.
    • CORS 적용 여부는 클라이언트(브라우저)에 달려 있습니다. 서버는 요청을 실행하고 응답을 반환하며, 오류를 반환하고 응답을 차단하는 것은 클라이언트입니다. 예를 들어 다음 도구는 서버 응답을 표시합니다.
  • 서버에서 브라우저가 원본 간 XHR 또는 Fetch API 요청을 실행할 수 있도록 허용하는 방법으로, 원래는 사용할 수 없는 방법입니다.
    • CORS가 없는 브라우저는 원본 간 요청을 수행할 수 없습니다. CORS JS이전에는 ONP 를 사용하여 이 제한을 회피했습니다. JSONP는 XHR을 사용하지 않고 태그를 <script> 사용하여 응답을 받습니다. 스크립트는 원본 간에 로드할 수 있습니다.

CORS 사양에서는 원본 간 요청을 사용하도록 설정하는 몇 가지 새로운 HTTP 헤더를 도입했습니다. 브라우저에서 CORS를 지원하는 경우 원본 간 요청에 대해 이러한 헤더를 자동으로 설정합니다. 사용자 지정 JavaScript 코드는 CORS를 사용하도록 설정하는 데 필요하지 않습니다.

배포된 샘플PUT 테스트 단추

다음은 Values 테스트 단추에서 https://cors1.azurewebsites.net/api/values로의 원본 간 요청 예제입니다. Origin 헤더는 다음과 같습니다.

  • 요청을 수행하는 사이트의 도메인을 제공합니다.
  • 필수로, 호스트와 달라야 합니다.

일반 헤더

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

응답 헤더

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

요청 헤더

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

요청에서 OPTIONS 서버는 응답에서 응답 헤더 헤더Access-Control-Allow-Origin: {allowed origin} 를 설정합니다. 예를 들어 배포된 샘플삭제 [EnableCors] 단추 OPTIONS 요청에는 다음 헤더가 포함됩니다.

일반 헤더

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

응답 헤더

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

요청 헤더

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

앞의 응답 헤더에서 서버는 응답에 Access-Control-Allow-Origin 헤더를 설정합니다. 이 헤더의 https://cors1.azurewebsites.net 값은 요청의 Origin 헤더와 일치합니다.

AllowAnyOrigin이 호출되면 와일드카드 값인 Access-Control-Allow-Origin: *이 반환됩니다. AllowAnyOrigin은 모든 원본을 허용합니다.

응답에 Access-Control-Allow-Origin 헤더가 포함되지 않으면 원본 간 요청이 실패합니다. 특히 브라우저는 요청을 허용하지 않습니다. 서버가 성공적인 응답을 반환하더라도 브라우저는 클라이언트 앱에서 응답을 사용할 수 있도록 하지 않습니다.

OPTIONS 요청 표시

기본적으로 Chrome 및 Edge 브라우저는 F12 도구의 네트워크 탭에 OPTIONS 요청을 표시하지 않습니다. 이러한 브라우저에서 OPTIONS 요청을 표시하려면 다음을 수행합니다.

  • chrome://flags/#out-of-blink-cors 또는 edge://flags/#out-of-blink-cors
  • 플래그를 사용하지 않도록 설정합니다.
  • 다시 시작합니다.

Firefox는 기본적으로 OPTIONS 요청을 표시합니다.

IIS의 CORS

IIS에 배포할 때 서버가 익명 액세스를 허용하도록 구성되지 않은 경우 Windows 인증 전에 CORS를 실행해야 합니다. 이 시나리오를 지원하려면 앱에 대해 IIS CORS 모듈을 설치하고 구성해야 합니다.

CORS 테스트

샘플 다운로드에는 CORS를 테스트하는 코드가 있습니다. 다운로드하는 방법을 참조하세요. 샘플은 Razor Pages가 추가된 API 프로젝트입니다.

public class StartupTest2
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapRazorPages();
        });
    }
}

경고

WithOrigins("https://localhost:<port>");샘플 코드 다운로드와 유사한 샘플 앱을 테스트하는 데만 사용해야 합니다.

다음 ValuesController는 테스트용 엔드포인트를 제공합니다.

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfoRick.Docs.Samples.RouteInfo NuGet 패키지가 제공하며 경로 정보를 표시합니다.

다음 방법 중 하나를 사용하여 위의 샘플 코드를 테스트합니다.

  • https://cors3.azurewebsites.net/에서 배포된 샘플 앱을 사용합니다. 샘플을 다운로드할 필요가 없습니다.
  • https://localhost:5001의 기본 URL을 사용하여 dotnet run에서 샘플을 실행합니다.
  • https://localhost:44398의 URL에 대해 포트가 44398로 설정된 샘플을 Visual Studio에서 실행합니다.

F12 도구와 함께 브라우저 사용:

  • 단추를 선택하고 네트워크 탭에서 헤더를 검토합니다.

  • PUT 테스트 단추를 선택합니다. OPTIONS 요청을 표시하기 위한 지침에 대해서는 OPTIONS 요청 표시를 참조하세요. PUT 테스트는 두 개의 요청, 즉 OPTIONS 사전 실행 요청과 PUT 요청을 만듭니다.

  • GetValues2 [DisableCors] 단추를 선택하여 실패한 CORS 요청을 트리거합니다. 이 문서에 설명된 대로 응답은 200 성공을 반환하지만 CORS 요청은 수행되지 않습니다. 콘솔 탭을 선택하여 CORS 오류를 확인합니다. 브라우저에 따라 다음과 유사한 오류가 표시됩니다.

    원본 'https://cors3.azurewebsites.net'에서 'https://cors1.azurewebsites.net/api/values/GetValues2'에 가져오기 위한 액세스 권한이 CORS 정책에 의해 차단됨: 요청된 리소스에 ‘Access-Control-Allow-Origin’ 헤더가 없음 사용자 요구에 불명확한 응답이 제공되면 요청 모드를 'no-cors'로 설정하여 CORS가 사용되지 않도록 설정된 리소스를 가져옵니다.

CORS 지원 엔드포인트는 curl, Fiddler 또는 Postman과 같은 도구를 사용하여 테스트할 수 있습니다. 도구를 사용하는 경우 Origin 헤더로 지정된 요청의 원본은 요청을 수신하는 호스트와 달라야 합니다. 요청이 Origin 헤더 값에 따라 ‘원본 간’이 아닌 경우:

  • CORS 미들웨어가 요청을 처리할 필요가 없습니다.
  • CORS 헤더가 응답에 반환되지 않습니다.

다음 명령은 curl을 사용하여 정보를 포함하는 OPTIONS 요청을 실행합니다.

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

엔드포인트 라우팅 및 [HttpOptions]를 통해 CORS 테스트

RequireCors를 사용하여 엔드포인트별로 CORS를 사용하도록 설정하면 자동 사전 실행 요청이 지원되지 않습니다. 엔드포인트 라우팅을 사용하여 CORS를 사용하도록 설정하는 다음 코드를 고려합니다.

public class StartupEndPointBugTest
{
    readonly string MyPolicy = "_myPolicy";

    // .WithHeaders(HeaderNames.ContentType, "x-custom-header")
    // forces browsers to require a preflight request with GET

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyPolicy,
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com",
                                        "https://cors1.azurewebsites.net",
                                        "https://cors3.azurewebsites.net",
                                        "https://localhost:44398",
                                        "https://localhost:5001")
                           .WithHeaders(HeaderNames.ContentType, "x-custom-header")
                           .WithMethods("PUT", "DELETE", "GET", "OPTIONS");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers().RequireCors(MyPolicy);
            endpoints.MapRazorPages();
        });
    }
}

다음 TodoItems1Controller는 테스트용 엔드포인트를 제공합니다.

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

배포된 샘플테스트 페이지에서 이전 코드를 테스트합니다.

엔드포인트에 [EnableCors]가 있고 사전 실행 요청에 응답하므로 Delete [EnableCors]GET [EnableCors] 단추는 성공합니다. 다른 엔드포인트는 실패합니다. JavaScript에서 다음을 보내기 때문에 GET 단추가 실패합니다.

 headers: {
      "Content-Type": "x-custom-header"
 },

다음 TodoItems2Controller는 유사한 엔드포인트를 제공하지만 OPTIONS 요청에 응답하는 명시적 코드를 포함합니다.

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    [EnableCors]  // Rquired for this path
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]  // Rquired for this path
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

배포된 샘플의 테스트 페이지에서 이전 코드를 테스트합니다. 컨트롤러 드롭다운 목록에서 사전 실행을 선택한 다음, 컨트롤러를 설정합니다. TodoItems2Controller 엔드포인트에 대한 모든 CORS 호출이 성공합니다.

추가 자료