Как лучше всего обрабатывать настраиваемые заголовки в CefSharp WinForms при работе с Blazor? - PullRequest
1 голос
/ 17 июня 2020

В настоящее время мы находимся в процессе создания веб-приложения для наших клиентов, которое построено с использованием компонентов Blazor SyncFusion. Части этого веб-приложения также предназначены для использования в качестве визуализации в нашем приложении WinForms, которое само по себе отлично работает. Однако с точки зрения авторизации мы сталкиваемся с одной большой проблемой.

Когда пользователь переходит к «(наше веб-приложение) / клиенты» в Chrome / Firefox / et c, его приветствует экран входа в систему Auth0, а после входа в систему отображается страница с SyncFusion на нем отображается список клиентов, относящихся к этому пользователю, с данными, полученными из одного из наших API. Когда пользователь открывает вкладку в нашем приложении WinForms, которая отображает ту же страницу с помощью браузера CefSharp, он автоматически входит в систему, и экран входа в систему Auth0 пропускается, потому что мы уже знаем токен (поскольку они необходимы для входа в систему). при запуске приложения WinForms) и передается в виде заголовка по запросу.

В приложении WinForms

Program.cs - инициализировать Chromium, когда приложение WinForms запущен

    private static void Main(string[] args)
    {
        //Program start up code
        ...
        ChromiumBrowser.InitChromium();
        ...
    }

ChromiumBrowser.cs - Захватите правильные сборки и установите CefSettings для браузера Chromium

    public static class ChromiumBrowser
    {
        public static void InitChromium()
        {
            //Set resolver to load correct assembly
            ...

            LoadApp();
        }

        public static ChromiumWebBrowser GetBrowser(string url)
        {
            return new ChromiumWebBrowser(url);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void LoadApp()
        {
            var settings = new CefSettings();
            settings.CefCommandLineArgs.Add("--disable-gpu-compositing");

            //Load correct assembly for x86/x64
            ...

            Cef.Initialize(settings, false, browserProcessHandler: null);
        }

        ...

    }

UBrowser.cs - Элемент управления WinForms для отображения ChromiumWebBrowser в. Пользовательский RequestHandler добавлен для добавления заголовка с токеном авторизации.

        public UBrowser(string token)
        {
            InitializeComponent();

            _chromiumBrowser = ChromiumBrowser.GetBrowser(null);
            _chromiumBrowser.RequestHandler = new BearerAuthRequestHandler(token);
            Controls.Add(_chromiumBrowser);
            _chromiumBrowser.Dock = DockStyle.Fill;
        }

        public void NavigateTo(string url)
        {
            _chromiumBrowser.Load(url);
        }

BearerAuthRequestHandler.cs - Пользовательский RequestHandler. OnBeforeResourceLoad, к сожалению, не вызывается для запросов WebSocket ...

    public class BearerAuthRequestHandler : RequestHandler
    {
        private readonly string _token;

        public BearerAuthRequestHandler(string token)
        {
            _token = token;
        }

        protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator,
            ref bool disableDefaultHandling)
        {
            if (!string.IsNullOrEmpty(_token))
            {
                return new CustomResourceHandlerFactory(_token);
            }
            else return base.GetResourceRequestHandler(chromiumWebBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling);
        }
    }

    internal class CustomResourceHandlerFactory : ResourceRequestHandler
    {
        private readonly string _token;

        public CustomResourceHandlerFactory(string token)
        {
            _token = token;
        }

        protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
        {
            if (!string.IsNullOrEmpty(_token))
            {
                var headers = request.Headers;
                headers["Authorization"] = $"Bearer {_token}";
                request.Headers = headers;
                return CefReturnValue.Continue;
            }
            else return base.OnBeforeResourceLoad(chromiumWebBrowser, browser, frame, request, callback);
        }
    }

В веб-приложении:

Client.razor - страница бритвы с элементом управления SyncFusion на ней . OnInitializedAsyn c запускается дважды из-за RenderMode.ServerPrerendered. Один раз, когда страница отображается в формате raw HTML во время предварительной отрисовки, и один раз, когда элемент управления SyncFusion запускает запрос веб-сокета Blazor для полной визуализации. Это сделано намеренно . Если RenderMode страницы имеет значение stati c, срабатывают только первые триггеры OnInitializedAsyn c, но элемент управления SyncFusion никогда не загружается, поскольку Blazor не помечается для запуска. Если для параметра RenderMode страницы задано значение server, предварительный рендеринг не выполняется, поэтому страница «загружается» медленнее для пользователя и запускается только второй OnInitializedAsyn c.

@page "/clients"
@attribute [Authorize]
@using Microsoft.AspNetCore.Routing
@using Microsoft.AspNetCore.Mvc.Localization
@using System.Text
@using OurWebApp.ViewModels
@using Syncfusion.Blazor.Grids
@using Syncfusion.Blazor.Navigations
@inject IApiConnector apiConnector
@inject NavigationManager navigationManager
@inject LinkGenerator linkGenerator

<div class="layout">
    <div id="list-container">
        <SfListView CssClass="e-list-template all" DataSource="@Clients">
            <ListViewFieldSettings Id="ID" Text="Name"></ListViewFieldSettings>
            <ListViewTemplates TValue="ClientViewModel">
                <Template>
                    @{
                        <div class="e-list-wrapper e-list-avatar e-list-multi-line">
                            <span class="e-avatar sf-icon-customer" style="font-size:16px; background:none;color:black"></span>
                            <span class="e-list-item-header">@context.Name</span>
                        </div>

                    }
                </Template>
            </ListViewTemplates>
        </SfListView>
    </div>
</div>

//Style
...

@code {

    IList<ClientViewModel> Clients = new IList<ClientViewModel>();

    protected async override Task OnInitializedAsync()
    {
        Clients = (await apiConnector.GetClients()).Clients.ToList() ?? new List<ClientViewModel>();
    }
}

ApiConnector.cs - Создается в момент вызова страницы, которая его включает. Когда RenderMode равен ServerPrerendered, он также вызывается дважды (возможно, без необходимости, но поскольку страница по существу перезагружается Blazor, я думаю, что мы мало что можем с этим поделать). В первый раз, когда страница предварительно визуализируется в HTML, заголовок для авторизации, отправленный CefSharp's OnBeforeResourceLoad, присутствует. Во второй раз, когда страница полностью визуализируется Blazor, заголовок отсутствует и CefSharp не запускает никаких событий.

        public ApiConnector(IHttpContextAccessor context, IAuthenticationSchemeProvider authenticationSchemeProvider)
        {
            bool bearerAuthentication = context.HttpContext.Request.Headers.Any(h => h.Key.Equals("Authorization"));
            client.BaseAddress = new Uri(/*our API uri*/);
            if (bearerAuthentication)
            {
                //If a "Bearer" scheme token exists in the headers, use it for authenticating the API. Auth0 was skipped.
                client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", context.HttpContext.GetTokenAsync("Bearer", "access_token").Result);
            }
            else
            {
               //Use the token supplied by a standard Auth0 log-in to authenticate the API.
               client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", context.HttpContext.GetTokenAsync("access_token").Result);
            }
        }

CEF в целом, похоже, не имеет возможности обрабатывать запросы веб-сокетов из box (https://magpcss.org/ceforum/viewtopic.php?f=6&t=16376), именно так Blazor сообщает странице о необходимости отрисовки себя после того, как она вызывается через элемент управления SyncFusion. Таким образом, заголовок теряется, и, по-видимому, никакие события не позволяют мне перехватить этот запрос на стороне клиента для повторной подачи заголовка. Указанный поток предлагает прокси-сервер WebSocket, но CefServer - это C ++, а не C#, и мне кажется, что это полезно только в том случае, если вы сами размещаете WebSocket, что, как я полагаю, здесь не так, если я не м непонимание как работают розетки?

В любом случае, мой вопрос очевиден: как мне повторно предоставить заголовок в запрос веб-сокета Blazor, чтобы наш API мог быть правильно авторизован? Если это вообще невозможно, каковы возможные обходные пути? Все, что связано с сохранением токена или возвращаемого значения api между начальным предварительным рендерингом HTML и рендерингом Blazor через веб-сервер через какой-то сервис, безусловно, возможно, но это кажется отрывочным и делает его больше не без состояния, что меньше, чем идеально подходит для работы с токенами аутентификации api, нет?

Кроме того, это можно смоделировать в самом Chrome с помощью надстройки ModHeader. Добавление токена и установка для отфильтрованной страницы только заголовка «(наше веб-приложение) / клиенты» приводит к тому же, что описано выше. Что интересно, отсутствие фильтрации страницы позволяет ModHeader предоставлять заголовки для любого запроса. Включая запрос веб-сокета Blazor. Итак, ясно, что Chrome должен иметь какой-то способ создать заголовок, который предоставляется независимо от того, чтобы это дополнение функционировало так же, как и оно, поэтому вопрос в том, может ли хром тоже? И если да, можно ли расширить CefSharp, чтобы раскрыть эту функциональность, если обходной путь не представляется возможным? . Надеюсь, я не упустил ничего необходимого, чтобы разобраться в ситуации.

...