ASP.NET(DotNetNuke)Google OAuth2登录:授权码转访问令牌求助
Google OAuth身份验证(DotNetNuke):授权码转访问令牌问题解决方案
一、先排查「Bad Request」的核心原因
你的自定义HttpWebRequest请求失败,大概率是以下几个细节问题:
- 参数未URL编码:code、redirect_uri等参数含特殊字符时会破坏请求格式
- ClientSecret存在多余空格:你第一个代码片段里的
ClientSecret = "client_secret "末尾有空格,和Google控制台配置不匹配 - 未捕获具体错误响应:当前
MakeWebRequest仅返回WebException的ToString,无法获取Google返回的详细错误信息 - 使用过时的用户信息端点:
oauth2/v1/userinfo已被弃用,建议改用oauth2/v3/userinfo
二、修复自定义HttpWebRequest代码
1. 替换为HttpClient(更简洁可靠)
废弃HttpWebRequest,改用HttpClient实现异步请求,同时增加错误响应捕获:
public async Task<string> MakeWebRequestAsync(string destinationUrl, string methodName, string contentType = "", string requestData = "") { try { using (var client = new HttpClient()) { HttpResponseMessage response; if (methodName.Equals("POST", StringComparison.OrdinalIgnoreCase)) { var content = new StringContent(requestData, Encoding.UTF8, contentType); response = await client.PostAsync(destinationUrl, content); } else { response = await client.GetAsync(destinationUrl); } // 强制读取响应内容,不管状态码,方便排查错误 string responseContent = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new Exception($"请求失败 [{(int)response.StatusCode}]: {responseContent}"); } return responseContent; } } catch (Exception ex) { return ex.ToString(); } }
2. 修改页面异步处理逻辑
首先在页面指令中开启异步:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Google.aspx.cs" Inherits="YourNamespace.Google" Async="true" %>
然后修改后台代码,对所有参数做URL编码,避免格式错误:
protected async void Page_Load(object sender, EventArgs e) { string ClientId = "你的ClientId"; string ClientSecret = "你的ClientSecret"; // 注意:去掉任何多余空格 string redirecturl = "https://url.com/google"; try { if (!IsPostBack) { string code = Request.QueryString["code"]; if (!string.IsNullOrEmpty(code)) { // 对所有参数做URL编码,避免特殊字符破坏请求 string encodedCode = Uri.EscapeDataString(code); string encodedClientId = Uri.EscapeDataString(ClientId); string encodedClientSecret = Uri.EscapeDataString(ClientSecret); string encodedRedirectUri = Uri.EscapeDataString(redirecturl); string parameters = $"code={encodedCode}&client_id={encodedClientId}&client_secret={encodedClientSecret}&redirect_uri={encodedRedirectUri}&grant_type=authorization_code"; string tokenResponse = await MakeWebRequestAsync("https://oauth2.googleapis.com/token", "POST", "application/x-www-form-urlencoded", parameters); JavaScriptSerializer serializer = new JavaScriptSerializer(); GoogleToken tokenInfo = serializer.Deserialize<GoogleToken>(tokenResponse); if (tokenInfo != null && !string.IsNullOrEmpty(tokenInfo.access_token)) { // 使用v3版本的用户信息端点 string userInfoUrl = $"https://www.googleapis.com/oauth2/v3/userinfo?access_token={Uri.EscapeDataString(tokenInfo.access_token)}"; string userInfoContent = await MakeWebRequestAsync(userInfoUrl, "GET"); GoogleInfo profile = serializer.Deserialize<GoogleInfo>(userInfoContent); ltrMSG.Text = $"用户ID:{profile.id},邮箱:{profile.email}"; // 此处加入DotNetNuke自定义登录逻辑 } } } } catch (Exception ex) { ltrMSG.Text += $"<br/>错误详情:{ex.ToString()}"; } } // 保留你的实体类 public class GoogleToken { public string access_token { get; set; } public string token_type { get; set; } public int expires_in { get; set; } public string id_token { get; set; } public string refresh_token { get; set; } } public class GoogleInfo { public string id { get; set; } public string email { get; set; } public bool verified_email { get; set; } public string name { get; set; } public string given_name { get; set; } public string family_name { get; set; } public string picture { get; set; } public string locale { get; set; } public string gender { get; set; } }
三、正确使用Google官方库(避免线程锁)
你遇到的ExchangeCodeForTokenAsync线程问题,是因为直接用.Result或.Wait()阻塞线程导致死锁,改用async/await即可解决:
protected async void Page_Load(object sender, EventArgs e) { string redirectUri = "https://url.com/google"; string[] Scopes = { "openid", "email", "profile" }; try { if (!IsPostBack) { string code = Request.QueryString["code"]; if (!string.IsNullOrEmpty(code)) { var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = new ClientSecrets { ClientId = "你的ClientId", ClientSecret = "你的ClientSecret" }, Scopes = Scopes, }); // 用await异步等待,禁止用.Result/.Wait() var tokenResponse = await flow.ExchangeCodeForTokenAsync("", code, redirectUri, CancellationToken.None); if (tokenResponse != null && !string.IsNullOrEmpty(tokenResponse.AccessToken)) { // 获取用户信息 var credential = new UserCredential(flow, "", tokenResponse); var oauthService = new OAuth2Service(new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = "你的DNN应用名称" }); var userInfo = await oauthService.Userinfo.Get().ExecuteAsync(); ltrMSG.Text = $"用户ID:{userInfo.Id},邮箱:{userInfo.Email}"; // 处理DotNetNuke自定义登录逻辑 } } } } catch (Exception ex) { ltrMSG.Text += $"<br/>错误详情:{ex.ToString()}"; } }
四、关键注意事项
- 重定向URI完全匹配:Google控制台配置的重定向URI必须和代码里的完全一致(包括HTTP/HTTPS、结尾斜杠等)
- ClientSecret无多余字符:检查配置里的ClientSecret是否有空格或换行符
- 异步操作规范:WebForms中必须在页面指令开启
Async="true",且用async/await处理所有异步方法 - 权限范围:确保申请的Scopes包含
email、profile等需要的权限
内容的提问来源于stack exchange,提问作者DKT
相关产品推荐
相关产品推荐

