C#之WebView2实现cookie登录,免登操作。

.NET兼职社区
.NET兼职社区
.NET兼职社区
.NET兼职社区
.NET兼职社区
.NET兼职社区

我实现的方式有三种,一种是通过JS注入cookie的方式,第二种是通过读取本地化存储的用户数据实现,第三种是发送请求的时候,对请求头进行修改。

一、JS注入的方式

这种方式需要逆向分析下需要的参数,对不熟悉JS逆向的不是很友好。
这里给出参考代码。
点击按钮,跳转到指定的url,页面加载完成后,注入JS。代码中有注释。

        private void ButtonGo_Click(object sender, RoutedEventArgs e)
        {
            if (webView != null && webView.CoreWebView2 != null)
            {
                webView.CoreWebView2.NavigationCompleted += ActionCompleted;
                webView.CoreWebView2.Navigate(addressBar.Text);
            }
        }

        #region 可以成功实现cookie登录
        private async void ActionCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e)
        {

            string cookieString = $"document.cookie='{_cookie}';";
            await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(cookieString); //通过js注入cookie  这种方式注入完成后需要刷新页面 
            // 刷新页面  isLoadCookie 表示cookie加载完成后就不要再重定向了   避免事件链无限触发
            if (!isLoadCookie)
            {
                webView.CoreWebView2.Navigate(addressBar.Text);
            }
            //加载完成
            isLoadCookie = true;
        }
        #endregion

二、读取用户本地化数据

这种方式是比较简单的方式,不需要分析别人网页,无脑加载就行。适合不想麻烦的人。

        #region 成功实现cookie登录 。用户数据存储 通过指定用户数据目录的存储和加载。也可以实现自动登录,如果你之前登录过,WebView2会把登录的用户信息存储在userDataFolder中,下次再指定相同的文件,WebView2会自动获取用户数据。如果想实现用户切换,可以为每个用户独立一个UserData文件夹,切换的时候加载不同的文件夹,就OK。
        string userDataFolder = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WebView2", "UserData");
        Directory.CreateDirectory(userDataFolder); // 确保目录存在
        var environment = await CoreWebView2Environment.CreateAsync(null, userDataFolder);
        await webView.EnsureCoreWebView2Async(environment);
        #endregion

这种方式是在你初始化的时候去加载指定的环境,EnsureCoreWebView2Async函数用于初始化WebView2,如果不指定环境,默认传null就行,webView会用默认环境加载。注释也写的毕竟详细,可以根据注释参考调整。

三、通过注入请求过滤器,通过请求头注入cookie

这种方式也需要分析请求头,和一些决定性参数。


            // 注册过滤器,这里假设我们对所有HTTP和HTTPS请求都感兴趣  https://www.bilibili.com/*
            webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);


            webView.CoreWebView2.WebResourceRequested +=  delegate (
              object? sender, CoreWebView2WebResourceRequestedEventArgs args)
            {
                CoreWebView2WebResourceContext resourceContext = args.ResourceContext;
                // 只拦截文档资源
                if (resourceContext != CoreWebView2WebResourceContext.Document)
                {
                    return;
                }
                if (!args.Request.Uri.Contains("bilibili")) return;
                CoreWebView2HttpRequestHeaders requestHeaders = args.Request.Headers;
                //foreach (var item in requestHeaders)
                //{
                //    await LogService.Instance.WriteInfoLog($"{item.Key}:{item.Value.ToString()}");
                //}
                requestHeaders.SetHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
                requestHeaders.SetHeader("cookie", ";;domain=.bilibili.com;path=/");
                requestHeaders.SetHeader("sec-ch-ua", "\"Chromium\";v=\"130\", \"Microsoft Edge\";v=\"130\", \"Not ? A_Brand\";v=\"99\", \"Microsoft Edge WebView2\";v=\"130\"");
                requestHeaders.SetHeader("sec-ch-ua-mobile", "?0");
                requestHeaders.SetHeader("sec-ch-ua-platform", "\"Windows\"");
                requestHeaders.SetHeader("upgrade-insecure-requests", "1");
                requestHeaders.SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0");
                Console.WriteLine();

初始化的时候添加请求过滤器,然后对符合要求的url动手。应该可以实现。修改请求头后再去加载网页。

四、WebView2一些常用的方法

4.1页面和后端进行通信

//当 CoreWebView2 控件接收到从网页发送的消息时,这个方法会被调用
      webView.CoreWebView2.WebMessageReceived += UpdateAddressBar;
      /**
       * AddScriptToExecuteOnDocumentCreatedAsync 方法:
          这个方法用于向 WebView2 控件注入一段 JavaScript 代码,这段代码会在每个新文档创建时自动执行。
          注入的脚本会在每个页面加载完成后立即运行,适用于所有新加载的页面。
          注入的 JavaScript 代码:
          "window.chrome.webview.postMessage(window.document.URL);"
          这段代码的作用是获取当前页面的 URL,并通过 postMessage 方法将 URL 发送到 WebView2 控件。
          window.chrome.webview.postMessage 是 WebView2 提供的一个 API,用于从网页向宿主应用程序发送消息。
       * **/
      await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.chrome.webview.postMessage(window.document.URL);");

      /**
       * AddScriptToExecuteOnDocumentCreatedAsync 方法:
              同上,这个方法用于向 WebView2 控件注入一段 JavaScript 代码,这段代码会在每个新文档创建时自动执行。
              注入的 JavaScript 代码:
              "window.chrome.webview.addEventListener('message', event => alert(event.data));"
              这段代码的作用是为 window.chrome.webview 对象添加一个消息事件监听器。
              当从宿主应用程序(即 WPF 应用程序)通过 PostWebMessageAsJson 或 PostWebMessageAsString 方法发送消息到网页时,这个监听器会被触发。
              event.data 包含从宿主应用程序发送的消息内容,alert(event.data) 会弹出一个警告框显示接收到的消息。
       * **/
      await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.chrome.webview.addEventListener(\'message\', event => alert(event.data));");
      void UpdateAddressBar(object sender, CoreWebView2WebMessageReceivedEventArgs args)
      {
          String uri = args.TryGetWebMessageAsString();
          addressBar.Text = uri;
          //给页面发送消息
          webView.CoreWebView2.PostWebMessageAsString(uri);
      }

通信过程,页面加载,页面会向后端发送消息,后端收到消息触发UpdateAddressBar函数,然后后端发消息给页面,页面收到消息,弹框。

前端代码:

<Window x:Class="MyWpfDotnetCoreWv2App.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyWpfDotnetCoreWv2App"
        xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <!--<DockPanel>
        <wv2:WebView2 Name="webView"
                  Source="https://www.bilibili.com/"
   />
    </DockPanel>-->

    <DockPanel>
        <DockPanel DockPanel.Dock="Top">
            <Button x:Name="ButtonGo" DockPanel.Dock="Right" Click="ButtonGo_Click" Content="Go"/>
            <Button x:Name="ButtonGetCookie" DockPanel.Dock="Right" Click="ButtonGetCookie_Click" Content="GetCookie"/>
            <TextBox Name = "addressBar"/>
        </DockPanel>
        <wv2:WebView2 Name = "webView" />
    </DockPanel>
</Window>

isTrusted 属性为 false的解决思路

isTrusted 属性表示事件是由用户操作(如实际的鼠标点击或键盘输入)触发的,还是由脚本(如 dispatchEvent 或 click 方法)触发的。具体来说:

  • isTrusted: true:表示事件是由用户操作触发的,例如用户实际点击了按钮。
  • isTrusted: false:表示事件是由脚本触发的,例如通过 dispatchEvent 或 click 方法触发的事件。

这种方式一般用于反爬虫,会使你的输入脚本和点击脚本失效。
解决办法:
使用上面说的通信方式,C#直接跟浏览器内核进行通信,这种通信方式的事件都是被认为是安全的。

      public static async Task InitializeWebView2(WebView2 webView)
      {

          // 注入 JavaScript 代码,用于在每个新文档创建时自动执行
          string injectScript = @"
      // 监听来自宿主应用程序的消息
      window.chrome.webview.addEventListener('message', event => {
          try {
              const eventData = JSON.parse(event.data);
              if (eventData.type === 'simulateInput') {
                  alert(eventData.type);
                  const inputSelector = eventData.inputSelector;
                  const value = eventData.value;
                  const buttonSelector = eventData.buttonSelector;

                  const inputElement = document.querySelector(inputSelector);
                  if (inputElement) {
                      simulateKeyEvents(inputElement, value, function() {
                          // 输入完成后触发发送按钮点击
                          const buttonElement = document.querySelector(buttonSelector);
                          if (buttonElement && !buttonElement.disabled) {
                              // 延迟点击按钮
                              setTimeout(() => {
                                  buttonElement.click();
                                  window.chrome.webview.postMessage(window.document.URL);
                              }, 1000); // 延迟1000毫秒
                          } else {
                              console.error('按钮不可点击');
                          }
                      });
                  } else {
                      console.error('未找到输入框元素');
                  }
              }
          } catch (error) {
              console.error('解析消息失败:', error);
          }
      });

      function simulateKeyEvents(element, value, callback) {
          let index = 0;

          function typeNextChar() {
              if (index < value.length) {
                  let char = value.charAt(index);
                  // 创建 keydown 事件
                  let keydown = new KeyboardEvent('keydown', { key: char, bubbles: true });
                  element.dispatchEvent(keydown);

                  // 创建 keypress 事件
                  let keypress = new KeyboardEvent('keypress', { key: char, bubbles: true });
                  element.dispatchEvent(keypress);

                  // 更新输入框的值
                  element.value += char;

                  // 创建 input 事件
                  let input = new InputEvent('input', { data: char, bubbles: true });
                  element.dispatchEvent(input);

                  // 创建 keyup 事件
                  let keyup = new KeyboardEvent('keyup', { key: char, bubbles: true });
                  element.dispatchEvent(keyup);

                  index++;
                  setTimeout(typeNextChar, 50); // 每次按键之间等待50毫秒
              } else {
                  if (callback) {
                      callback(); // 输入完成后调用回调函数
                  }
              }
          }

          // 将焦点设置到输入框上
          element.focus();

          typeNextChar();
      }
  ";


          await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(injectScript);

          // 注册消息接收处理器
          webView.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
      }
        public static async Task InputElement(WebView2 webView, string inputSelector, string value, string buttonSelector)
        {
            // 发送消息给网页
            string message = $"{{\"type\": \"simulateInput\", \"inputSelector\": \"{inputSelector}\", \"value\": \"{value}\", \"buttonSelector\": \"{buttonSelector}\"}}";
            webView.CoreWebView2.PostWebMessageAsString(message);
            await semaphore.WaitAsync();
        }
        private static void CoreWebView2_WebMessageReceived(object? sender, CoreWebView2WebMessageReceivedEventArgs e)
        {
            // 处理从网页发送的消息
            string message = e.TryGetWebMessageAsString();
            Console.WriteLine("Received message from web page: " + message);
            semaphore.Release();
        }

这样就可以绕开页面级别的脚本检测。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值