Blazor-запрос заблокирован политикой CORS на PHP API - PullRequest
1 голос
/ 04 ноября 2019

Я настраиваю PHP API и веб-страницу на основе клиентской версии Blazor. Но по какой-то причине запускается CORS, и мой процесс входа в систему или любые запросы к моим страницам PHP приводят к ошибкам CORS.

Я начал тестировать мой PHP API с консольным приложением C # и приложением Blazor, которое я пытался использовать безлюбой доступ к базе данных для проверки функциональности. Blazor сейчас работает с Preview 9. Версия PHP - 5.3.8. Я мог бы теоретически обновить его, но на нем запущено несколько других активных проектов, и у меня нет тестовой среды. MySQL версии 5.5.24.

Сначала я подумал, что это могло быть потому, что я запускал его на своей локальной машине, поэтому я отправил его на веб-сайт, где также работают PHP и MySQL. Тем не менее я сталкиваюсь с этой ошибкой CORS.

Я все еще просто проверяю это, поэтому я попытался установить его, чтобы разрешить любое происхождение. До этого у меня не было опыта работы с CORS. Я уверен, что я должен иметь возможность добавлять код PHP в каждый файл, к которому у меня есть доступ, что должно разрешать CORS, но, поскольку все они должны быть на одном и том же веб-сайте, я считаю, что 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();

C # код с использованием введенного 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.

Это потому, что на нем работает клиентская сторона, и это вызывает CORS? Но это, похоже, не объясняет, почему я не могу заставить его разрешить все.

Обновление: мой код C # в 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, включающего учетные данные. Другой альтернативой было добавить эту строку в автозагрузку:

WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;

Тогда я мог бы продолжать делать то, что делал для начала, используя GetStringAsync, так как он становится значением по умолчанию. await Http.GetStringAsync (API_RequestLoginTokenEndPoint);

Таким образом, все предложенные решения Agua From Mars сработали. Но я столкнулся с проблемой браузера, когда он как-то сохранял проблему с CORS в кеше даже после того, как ее удалось решить, поэтому казалось, что ничего не изменилось. Некоторые изменения кода будут показывать другой результат, но я думаю, что часть CORS осталась в живых. С Chrome это помогло открыть новую панель или окно. В моем браузере Opera этого было недостаточно, мне пришлось закрыть все панели с открытым сайтом, чтобы убедиться, что он очистит кеш, а затем открытие нового окна или панели с сайтом также работает в Opera. Я уже в обоих браузерах пытался использовать ctrl-F5 и Shift-F5, чтобы заставить их очистить кеш. Это ничего не изменило.

Надеюсь, это поможет другим не тратить 2-3 дня на подобные проблемы.

1 Ответ

1 голос
/ 04 ноября 2019

обновление 3.1-preview3

В 3.1-preview3 мы не можем использовать опцию извлечения для сообщения, глобальная опция

WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;

WebAssemblyHttpMessageHandler была удалена. HttpMessageHanlder используется WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler из WebAssembly.Net.Http, но не включайте WebAssembly.Net.Http в ваши зависимости, иначе приложение не запустится.

Если вы хотите использовать HttpClientFactory, вы можете реализоватьнапример:

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 вам необходимо сообщить Fetch API для отправки учетных данных (куки-файлы)и заголовок авторизации).

Это описывается в Blazor doc Обмен ресурсами между источниками (CORS)

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

пример:

@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 static proprerty.

Или вы можете реализовать DelegatingHandler и установить его в DI с помощью 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>();

Затем вы можете создать HttpClient для своего API с помощью IHttpClientFactory.CreateClient(httpClientName)

Для использования IHttpClientFactory вам необходимо установить Microsoft.Extensions.Http package.

3.0-preview3 => 3.0-preview9

Заменить WebAssemblyHttpMessageHandler на BlazorHttpMessageHandler

...