首页 > 解决方案 > 为什么跨域资源共享 (CORS) 不会在服务器端阻塞?

问题描述

我创建了 2 个项目:ClientServer

客户端是一个 Razor Web 应用程序,在索引页面上包含用于调用 api 的 javascript。它托管在http://localhost:8000下。

索引.cshtml

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<div class="container">
    <div class="row">
        <div class="col-6">
            <button id="sender-get">GET</button>

            <div id="content-get"></div>
        </div>
        <div class="col-6">
            <button id="sender-post">POST</button>

            <div id="content-post"></div>
        </div>
    </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.0/js/bootstrap.min.js" integrity="sha256-oKpAiD7qu3bXrWRVxnXLV1h7FlNV+p5YJBIr8LOCFYw=" crossorigin="anonymous"></script>

<script>
    $(document).ready(() => {
        $('#sender-get').click(() => {
            $.get("http://localhost:9000/weatherforecast")
                .done(() => {
                    $('#content-get').text('done');
                })
                .fail(() => {
                    $('#content-get').text('fail');
                });
        });

        $('#sender-post').click(() => {
            $.post("http://localhost:9000/weatherforecast")
                .done(() => {
                    $('#content-post').text('done');
                })
                .fail(() => {
                    $('#content-post').text('fail');
                });
        });
    });
</script>

服务器是一个带有天气预报模板的 ASPNET Core (3.1) Web Api。它托管在http://localhost:9000下。它使用 CORS 中间件并配置为接受来自http://localhost:5000的请求。

WeatherForecastController.cs

namespace Server.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }



        [HttpPost]
        public IEnumerable<WeatherForecast> Post()
        {
            return null;
        }
    }
}

启动.cs

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

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddDefaultPolicy(policy =>
                {
                    policy.WithOrigins("http://localhost:5000");
                });
            });

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseCors();

            app.UseRouting();

            app.UseAuthorization();

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

当我点击POST时,请求失败,这是一个正确的响应:

在此处输入图像描述

但是如果你在 api 控制器上设置一个断点,它仍然会命中 Post() 方法:

在此处输入图像描述

标签: c#asp.net-web-apicors

解决方案


被击中的控制器端点是预期的行为。

CORS 限制在浏览器端强制执行,而不是在服务器端强制执行。您提到的 ASP.NET Core CORS 中间件也是如此。如果可能,中间件的职责仅仅是指示浏览器首先不要发送不允许的请求。然而,一些“简单”的请求(比如一些非常简单的 GET 和 POST)总是会到达服务器——这就是你观察到的。

CORS 一开始可能听起来很棘手,但就像生活中的所有事情一样,一旦你看看幕后,这一切都是有道理的。

跨域资源共享 (CORS) 是一种机制,它使用额外的 HTTP 标头来告诉浏览器让在一个来源上运行的 Web 应用程序可以访问来自不同来源的选定资源。当 Web 应用程序请求的资源与其自己的来源(域、协议或端口)不同时,它会执行跨域 HTTP 请求。

https://developer.mozilla.org/docs/Web/HTTP/CORS

对于简单的请求(像大多数HEAD/GET但也有一些简单的请求POST,请参阅https://developer.mozilla.org/docs/Web/HTTP/CORS#Simple_requests了解详细信息),浏览器只执行请求并检查响应中的 CORS 标头( like Access-Control-Allow-Origin) 来确定是否允许请求或是否应丢弃结果。

对于预先发送的请求(例如PUT/PATCH/DELETE但也GET/POST带有非标准标头或内容类型,请参阅https://developer.mozilla.org/docs/Web/HTTP/CORS#Preflighted_requests了解详细信息),浏览器发出所谓的 pre-带有OPTIONhttp 动词的飞行请求,以确定端点是否允许跨源请求。

浏览器对可能完全由于您观察到的行为而改变服务器上的数据的任何请求执行预先飞行前请求。如果它不使用无害的OPTION前期,服务器将在收到请求时删除相应的请求,DELETE尽管该请求本应被服务器的 CORS 策略拒绝。

但是,浏览器不会像大多数那样对简单请求进行飞行前请求,GET因为这些请求是无害的。这就是为什么您的断点仍然被命中但响应随后被浏览器丢弃的原因。这也是您永远不应该更改GET请求中的数据而使用专用动词的原因之一PUT/PATCH/DELETE:)


推荐阅读