C# PHP API上的CORS策略阻止Blazor请求

C# PHP API上的CORS策略阻止Blazor请求,c#,cors,client,blazor,.net-core-3.0,C#,Cors,Client,Blazor,.net Core 3.0,我正在基于客户端Blazor设置一个PHP API和一个网页。但由于某些原因,CORS被触发,我的登录过程或对PHP页面的任何请求都会导致CORS错误 我开始用一个C#控制台应用程序和Blazor应用程序测试我的PHP API,我尝试在没有任何数据库访问的情况下使用它来测试功能。Blazor现在正在运行预览9。PHP版本是5.3.8。理论上我可以更新它,但是其他几个活动项目正在运行,我没有任何测试环境。MySQL版本5.5.24 首先我想可能是因为我在本地机器上运行它,所以我把它推到了PHP和M

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

我开始用一个C#控制台应用程序和Blazor应用程序测试我的PHP API,我尝试在没有任何数据库访问的情况下使用它来测试功能。Blazor现在正在运行预览9。PHP版本是5.3.8。理论上我可以更新它,但是其他几个活动项目正在运行,我没有任何测试环境。MySQL版本5.5.24

首先我想可能是因为我在本地机器上运行它,所以我把它推到了PHP和MySQL也在运行的网站上。但我还是遇到了这个错误

我还在测试这个,所以我试着将它设置为允许任何来源。在此之前,我没有任何CORS的经验。我很确定我应该能够在我访问的每个文件中添加允许CORS的PHP代码,但是因为它应该都在同一个网站上,我想CORS甚至不应该是相关的

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#代码:

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返回令牌一样

是不是因为它运行的客户端可能会触发CORS?但这似乎并不能解释为什么我不能让它允许一切

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

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#代码是来自Mars的Agua建议的解决方案,它解决了将SendAsync与HttpRequestMessage一起使用并向其中添加Fetch属性include credentials的问题。另一种选择是将此行添加到启动中:

WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
然后我可以继续做我开始做的事情,在默认情况下使用GetStringAsync。 等待Http.GetStringAsync(API_RequestLoginTokenEndPoint)

因此,Agua从火星提出的所有解决方案都有效。但我遇到了一个浏览器问题,即使在解决了CORS问题之后,它仍以某种方式将CORS问题保留在缓存中,因此看起来什么都没有改变。一些代码更改将显示不同的结果,但我猜CORS部分仍然保持活动状态。通过Chrome,它帮助打开了一个新的窗格或窗口。在我的Opera浏览器中,这还不够,我必须在站点打开时关闭所有窗格,以确保它将清除缓存,然后打开一个新窗口或窗格,该站点也可以在Opera中工作。我已经在两个浏览器中尝试使用ctrl-F5和Shift-F5来清除缓存。这并没有改变任何事情

我希望这将帮助其他人避免在这样的问题上花费2-3天。

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

WebAssemblyHttpMessageHandlerOptions.DefaultCredentials=FetchCredentialsOption.Include;
WebAssemblyHttpMessageHandler
已被删除。使用的
httpmessagehandler
WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler
中的
WebAssembly.Net.Http
,但不要将
WebAssembly.Net.Http
包含在从属项中,否则应用程序将无法启动

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

公共类CustomDelegationHandler:DelegationHandler
{
私有只读IUserStore\u userStore;
私有只读HttpMessageHandler\u InnerHandler;
私有只读MethodInfo\u方法;
公共CustomDelegationHandler(IUserStore userStore,HttpMessageHandler InnerHandler)
{
_userStore=userStore??抛出新的ArgumentNullException(nameof(userStore));
_innerHanler=innerHanler??抛出新的ArgumentNullException(nameof(innerHanler));
var type=innerHanler.GetType();
_method=type.GetMethod(“SendAsync”,BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod)??抛出新的InvalidOperationException(“无法获取SendAsync方法”);
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials=FetchCredentialsOption.Include;
}
受保护的覆盖任务SendAsync(HttpRequestMessage请求,CancellationToken CancellationToken)
{
request.Headers.Authorization=新的AuthenticationHeaderValue(\u userStore.AuthenticationScheme,\u userStore.AccessToken);
返回_method.Invoke(_innerHanler,new object[]{request,cancellationToken})作为任务;
}
}

public void配置服务(IServiceCollection服务)
{
services.AddTransient(p=>
{
var wasmHttpMessageHandlerType=Assembly.Load(“WebAssembly.Net.Http”)
.GetType(“WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler”);
var constructor=wasmHttpMessageHandlerType.GetConstructor(Array.Empty());
将constructor.Invoke(Array.Empty())作为HttpMessageHandler返回;
})
.AddTransient()
.AddHttpClient(“MyApiHttpClientName”)
.AddHttpMessageHandler();
}
3.0->3.1-预览2 在Blazor客户端,您需要告诉服务器发送凭据(cookies和授权头)

在Blazor文档中对其进行了描述

requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs]=new
{ 
凭据=FetchCredentialsOption。包括
};
例:

@使用System.Net.Http
@使用System.Net.Http.Header
@注入HttpClient Http
@代码{
专用异步任务PostRequest()
{
Http.DefaultRequestHeaders.Authorization=
新的AuthenticationHeaderValue(“承载者”,“{OAUTH令牌}”);
var requestMessage=new-HttpRequestMessage()
{
方法=新的HttpMethod(“POST”),
RequestUri=新的Uri(“https://localhost:10000/api/TodoItems"),
内容=
新内容(
@{“名称”:新的待办事项