Blazor请求被PHP API上的CORS策略阻止 [英] Blazor Request blocked by CORS policy on PHP API

查看:189
本文介绍了Blazor请求被PHP API上的CORS策略阻止的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在基于客户端Blazor设置一个PHP API和一个网页.但是由于某种原因,触发了CORS,并且我的登录过程或对我的PHP页面的任何请求都导致CORS错误.

I am setting up a PHP API and a web-page based on client-side Blazor. But for some reason CORS is triggered and my login process or any requests to my PHP pages result in CORS errors.

我开始使用C#控制台应用程序和Blazor应用程序测试我的PHP API,我尝试使用没有任何数据库访问权限来测试功能. Blazor现在与Preview 9一起运行.PHP版本为5.3.8.从理论上讲,我可以对其进行更新,但是正在运行其他几个活动项目,而我没有任何测试环境. MySQL版本5.5.24.

I started out testing my PHP API with a C# console app and the Blazor app, I tried using without any database access to test the functionality. The Blazor is right now running with Preview 9. The PHP version is 5.3.8. I could in theory update it, but several other active projects are running on it and I do not have any test environment. MySQL version 5.5.24.

首先我发现可能是因为我在本地计算机上运行它,所以我将其推送到同时运行PHP和MySQL的网站.仍然我遇到了这个CORS错误.

First I figured it might have been because I was running it on my local machine, so I pushed it to the website where the PHP and MySQL is also running. Still I run into this CORS error.

我仍在测试,因此我尝试将其设置为允许任何来源.在此之前,我还没有任何有关CORS的经验.可以肯定的是,我应该能够在我访问的每个文件中都添加PHP代码,以允许CORS,但是由于所有文件都应该位于同一网站上,因此我认为CORS甚至不相关吗?

I am still just testing this, so I have tried setting it to allow any origin. I have not had any experience with CORS before this. Pretty sure I ought to be able to add PHP code in each file I access that should allow CORS, but since it should all be on the same website, I figure CORS should not even be relevant?

PHP代码:

function cors() {

// Allow from any origin
if (isset($_SERVER['HTTP_ORIGIN'])) {
    // Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
    // you want to allow, and if so:
    header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
    header('Access-Control-Allow-Credentials: true');
    header('Access-Control-Max-Age: 86400');    // cache for 1 day
}

// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {

    if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
        // may also be using PUT, PATCH, HEAD etc
        header("Access-Control-Allow-Methods: GET, POST, OPTIONS");         

    if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
        header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");

    exit(0);
}

echo "You have CORS!";
}
cors();

使用注入的HttpClient的C#代码:

C# code using the injected HttpClient:

var resp = await Http.GetStringAsync(link);

我得到的错误是:

Access to fetch at 'https://titsam.dk/ntbusit/busitapi/requestLoginToken.php' from origin 'https://www.titsam.dk' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

我希望得到的答复是,我使用的链接像登录API一样返回登录令牌.

The response I hoped to get was that the link I use return a token for the login as it does for my API.

是因为它可能正在运行客户端,并且这会触发CORS吗?但这似乎并不能解释为什么我不能让它全部允许.

Is it because its running client side maybe and this triggers CORS? But that does not seem to explain why I cannot make it allow all.

更新: 我在OnInitializedAsync中的C#代码:

Update: My C# code in OnInitializedAsync:

link = API_RequestLoginTokenEndPoint;

Http.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", "basic:testuser:testpass");

var requestMessage = new HttpRequestMessage(HttpMethod.Get, link);

requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
    credentials = "include"
};

var response = await Http.SendAsync(requestMessage);
var responseStatusCode = response.StatusCode;
var responseBody = await response.Content.ReadAsStringAsync();

output = responseBody + " " + responseStatusCode;

更新2: 终于可以了.我链接的C#代码是Agua From Mars提出的解决方案,它解决了将SendAsync与HttpRequestMessage一起使用并向其中添加Fetch属性包含凭据的问题.另一种选择是将此行添加到启动中:

Update 2: It finally works. The C# code I linked is the solution Agua From Mars suggested and it solved the problem to use SendAsync with a HttpRequestMessage and adding the Fetch property include credentials to it. Another alternative was to add this line to the startup:

WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;

然后,我可以继续使用GetStringAsync作为默认设置,开始做我想做的事情. 等待Http.GetStringAsync(API_RequestLoginTokenEndPoint);

Then I could keep doing what I did to begin with, using GetStringAsync as it becomes the default. await Http.GetStringAsync(API_RequestLoginTokenEndPoint);

因此,Agua From Mars建议的所有解决方案都有效.但是我遇到了一个浏览器问题,即使解决了问题,它仍然以某种方式将CORS问题保留在缓存中,因此似乎没有任何变化.某些代码更改将显示不同的结果,但是我想CORS部分仍然有效.借助Chrome,它有助于打开新的窗格或窗口.在我的Opera浏览器中,这还不够,我不得不在站点打开的情况下关闭所有窗格,以确保清除缓存,然后在站点中也可以在站点中打开一个新窗口或窗格.我已经在两种浏览器中尝试使用ctrl-F5和Shift-F5使其清除缓存.这并没有改变任何东西.

So all the solutions Agua From Mars suggested worked. But I encountered a browser problem, where it kept the CORS issue in the cache somehow even after it had gotten solved, so it seemed like nothing had changed. Some code changes would show a different result, but I guess the CORS part was kept alive. With Chrome it helped opening a new pane or window. In my Opera browser this was not enough, I had to close all panes with the site open to ensure it would clear the cache and then opening a new window or pane with the site works in Opera as well. I had already in both browsers trying to use ctrl-F5 and Shift-F5 to get them to clear the cache. This did not change anything.

我希望这可以帮助其他人避免在此类问题上花费2-3天.

I hope this will help others avoid spending 2-3 days on an issue like this.

推荐答案

更新3.1-preview3

在3.1-preview3中,我们不能对每条消息使用fetch选项,该选项是全局的

update 3.1-preview3

In 3.1-preview3, we cannot use the fetch option per message, the options is global

WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;

WebAssemblyHttpMessageHandler已被删除.使用的HttpMessageHanlderWebAssembly.Net.Http中的WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler,但是在您的依赖中不要包含WebAssembly.Net.Http,否则应用程序将无法启动.

WebAssemblyHttpMessageHandler has been removed. The HttpMessageHanlder used is WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler from WebAssembly.Net.Http but don't include WebAssembly.Net.Http in your depencies or the application will failled to launch.

如果要使用HttpClientFactory,则可以这样实现:

If you want to use the HttpClientFactory you can implement like that :

public class CustomDelegationHandler : DelegatingHandler
{
    private readonly IUserStore _userStore;
    private readonly HttpMessageHandler _innerHanler;
    private readonly MethodInfo _method;

   public CustomDelegationHandler(IUserStore userStore, HttpMessageHandler innerHanler)
   {
       _userStore = userStore ?? throw new ArgumentNullException(nameof(userStore));
       _innerHanler = innerHanler ?? throw new ArgumentNullException(nameof(innerHanler));
       var type = innerHanler.GetType();
       _method = type.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod) ?? throw new InvalidOperationException("Cannot get SendAsync method");
       WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
   }
   protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   {
        request.Headers.Authorization = new AuthenticationHeaderValue(_userStore.AuthenticationScheme, _userStore.AccessToken);            
        return _method.Invoke(_innerHanler, new object[] { request, cancellationToken }) as Task<HttpResponseMessage>;
   }
}


public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient(p =>
    {
        var wasmHttpMessageHandlerType =  Assembly.Load("WebAssembly.Net.Http")
                        .GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
        var constructor = wasmHttpMessageHandlerType.GetConstructor(Array.Empty<Type>());
        return constructor.Invoke(Array.Empty<object>()) as HttpMessageHandler;
    })
    .AddTransient<CustomDelegationHandler>()
    .AddHttpClient("MyApiHttpClientName")
    .AddHttpMessageHandler<CustonDelegationHandler>();
}

3.0-> 3.1-preview2

在Blazor客户端,您需要告知获取API 发送凭据(cookie和授权标头).

3.0 -> 3.1-preview2

On Blazor client side your need to tell to the Fetch API to send credentials (cookies and authorization header).

它在 Blazor文档中进行了描述例如:

@using System.Net.Http
@using System.Net.Http.Headers
@inject HttpClient Http

@code {
    private async Task PostRequest()
    {
        Http.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", "{OAUTH TOKEN}");

        var requestMessage = new HttpRequestMessage()
        {
            Method = new HttpMethod("POST"),
            RequestUri = new Uri("https://localhost:10000/api/TodoItems"),
            Content = 
                new StringContent(
                    @"{""name"":""A New Todo Item"",""isComplete"":false}")
        };

        requestMessage.Content.Headers.ContentType = 
            new System.Net.Http.Headers.MediaTypeHeaderValue(
                "application/json");

        requestMessage.Content.Headers.TryAddWithoutValidation(
            "x-custom-header", "value");

        requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
        { 
            credentials = FetchCredentialsOption.Include
        };

        var response = await Http.SendAsync(requestMessage);
        var responseStatusCode = response.StatusCode;
        var responseBody = await response.Content.ReadAsStringAsync();
    }
}

您可以使用WebAssemblyHttpMessageHandlerOptions.DefaultCredentials静态属性来全局设置此选项.

You can set up this option globaly with WebAssemblyHttpMessageHandlerOptions.DefaultCredentials static proprerty.

或者您可以实现DelegatingHandler并使用HttpClientFactory在DI中进行设置:

Or you can implement a DelegatingHandler and set it up in DI with the HttpClientFactory:

    public class CustomWebAssemblyHttpMessageHandler : WebAssemblyHttpMessageHandler
    {
        internal new Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return base.SendAsync(request, cancellationToken);
        }
    }

    public class CustomDelegationHandler : DelegatingHandler
    {
        private readonly CustomWebAssemblyHttpMessageHandler _innerHandler;

        public CustomDelegationHandler(CustomWebAssemblyHttpMessageHandler innerHandler)
        {
            _innerHandler = innerHandler ?? throw new ArgumentNullException(nameof(innerHandler));
        }
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
            {
                credentials = "include"
            };
            return _innerHandler.SendAsync(request, cancellationToken);
        }
    }

Setup.ConfigureServices

services.AddTransient<CustomWebAssemblyHttpMessageHandler>()
    .AddTransient<WebAssemblyHttpMessageHandler>()
    .AddTransient<CustomDelegationHandler>()
    .AddHttpClient(httpClientName)
    .AddHttpMessageHandler<CustomDelegationHandler>();

然后,您可以使用IHttpClientFactory.CreateClient(httpClientName)

要使用IHttpClientFactory,您需要安装Microsoft.Extensions.Http软件包.

To use the IHttpClientFactory you need to install Microsoft.Extensions.Http package.

BlazorHttpMessageHandler

这篇关于Blazor请求被PHP API上的CORS策略阻止的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆