WinForms中WebBrowser控件加载Google Calendar出现认证安全错误如何解决
WinForms WebBrowser控件加载Google Calendar OAuth认证报错解决方案
问题原因
出现“浏览器不安全”报错是因为WinForms自带的WebBrowser控件默认基于IE内核,谷歌早在2021年就停止支持IE系列浏览器发起的OAuth认证请求,直接判定该类嵌入式浏览器为不安全环境,阻止认证流程。
修复方案
方案1:修改WebBrowser内核模拟版本(不推荐,仅作为临时兼容方案)
WinForms WebBrowser默认模拟IE7运行,可通过修改注册表强制其使用IE11内核:
- 32位系统注册表路径:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION - 64位系统32位程序路径:
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION
在对应路径下新建DWORD值,名称为你的程序exe文件名(比如CalendarApp.exe),值设置为11000(十进制),代表强制使用IE11标准模式。
注:该方案兼容性差,部分谷歌认证场景仍会拦截,且IE11已停止维护,建议优先使用方案2
方案2:替换为WebView2控件(推荐)
WebView2是微软推出的基于Chromium内核的嵌入式浏览器控件,完全兼容谷歌OAuth认证规则:
- 项目通过NuGet安装
Microsoft.Web.WebView2包 - 替换原有WebBrowser控件为WebView2控件
- 原有认证逻辑无需大幅修改,仅需调整页面加载、文档读取的对应API即可正常完成认证
其他必要配置检查
- 登录谷歌云控制台,进入你的OAuth客户端配置页,确保
授权重定向URI列表中已添加你代码中使用的https://calendar.google.com/,完全匹配不得有差异 - 检查OAuth同意屏幕配置:如果应用处于测试状态,仅你添加到测试用户列表的谷歌账号可以完成认证,若要公开使用需要将应用发布为正式状态并完成谷歌验证
- 你当前代码中泄露了有效ClientID和ClientSecret,请立即前往谷歌云控制台删除该凭证,重新生成新的ClientID和ClientSecret,避免被他人盗用产生资损
原有代码参考
Kalender控件代码
public partial class Kalender : UserControl { public string clientId = "897492813624-glvfpm6heik1fqr7s2mjtl6eme5ad8bq.apps.googleusercontent.com"; public string redirectUri = "https://calendar.google.com/"; public string clientSecret = "HYGFBEd_BZPcWcrPopsX4uZr"; public string redirectURI = "https://calendar.google.com/"; public AuthResponse access; public Kalender() { InitializeComponent(); webKalender.Navigate(AuthResponse.GetAutenticationURI(clientId, redirectUri)); } private void webKalender_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { string Mytitle = ((WebBrowser)sender).DocumentTitle.ToLower(); if (Mytitle.IndexOf("success code=") > -1) { webKalender.Visible = false; // searching the body for our code string AuthCode = webKalender.DocumentTitle.Replace("Success code=", ""); string webText = ((WebBrowser)sender).DocumentText; int start = webText.IndexOf("id=\"code\""); start = webText.IndexOf(AuthCode, start); int end = webText.IndexOf('"', start); string authCode = webText.Substring(start, end - start); //Exchange the code for Access token and refreshtoken. access = AuthResponse.Exchange(authCode, clientId, clientSecret, redirectURI); //processAccess(); } } }
AuthResponse类代码
public class AuthResponse { private string access_token; public string Access_token { get { // Access token lasts an hour if its expired we get a new one. if (DateTime.Now.Subtract(created).Hours > 1) { refresh(); } return access_token; } set { access_token = value; } } public string refresh_token { get; set; } public string clientId { get; set; } public string secret { get; set; } public string expires_in { get; set; } public DateTime created { get; set; } //"{\n \"access_token\" : \"ya29.kwFUj-la2lATSkrqFlJXBqQjCIZiTg51GYpKt8Me8AJO5JWf0Sx6-0ZWmTpxJjrBrxNS_JzVw969LA\",\n \"token_type\" : \"Bearer\",\n \"expires_in\" : 3600,\n \"refresh_token\" : \"1/ejoPJIyBAhPHRXQ7pHLxJX2VfDBRz29hqS_i5DuC1cQ\"\n}" public static AuthResponse get(string response) { AuthResponse result = (AuthResponse)JsonConvert.DeserializeObject(response); result.created = DateTime.Now; // DateTime.Now.Add(new TimeSpan(-2, 0, 0)); //For testing force refresh. return result; } public void refresh() { var request = (HttpWebRequest)WebRequest.Create("https://accounts.google.com/o/oauth2/token"); string postData = string.Format("client_id={0}&client_secret={1}&refresh_token={2}&grant_type=refresh_token", this.clientId, this.secret, this.refresh_token); var data = Encoding.ASCII.GetBytes(postData); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = data.Length; using (var stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } var response = (HttpWebResponse)request.GetResponse(); var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); var refreshResponse = AuthResponse.get(responseString); this.access_token = refreshResponse.access_token; this.created = DateTime.Now; } public static AuthResponse Exchange(string authCode, string clientid, string secret, string redirectURI) { var request = (HttpWebRequest)WebRequest.Create("https://accounts.google.com/o/oauth2/token"); string postData = string.Format("code={0}&client_id={1}&client_secret={2}&redirect_uri={3}&grant_type=authorization_code", authCode, clientid, secret, redirectURI); var data = Encoding.ASCII.GetBytes(postData); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = data.Length; using (var stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } var response = (HttpWebResponse)request.GetResponse(); var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); var x = AuthResponse.get(responseString); x.clientId = clientid; x.secret = secret; return x; } public static Uri GetAutenticationURI(string clientId, string redirectUri) { string scopes = "https://www.googleapis.com/auth/calendar"; if (string.IsNullOrEmpty(redirectUri)) { redirectUri = "urn:ietf:wg:oauth:2.0:oob"; } string oauth = string.Format("https://accounts.google.com/o/oauth2/auth?client_id={0}&redirect_uri={1}&scope={2}&response_type=code", clientId, redirectUri, scopes); return new Uri(oauth); } }
报错截图


内容的提问来源于stack exchange,提问作者Jooonnnaass
相关产品推荐
相关产品推荐

