You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET(DotNetNuke)Google OAuth2登录:授权码转访问令牌求助

Google OAuth身份验证(DotNetNuke):授权码转访问令牌问题解决方案

一、先排查「Bad Request」的核心原因

你的自定义HttpWebRequest请求失败,大概率是以下几个细节问题:

  1. 参数未URL编码:code、redirect_uri等参数含特殊字符时会破坏请求格式
  2. ClientSecret存在多余空格:你第一个代码片段里的ClientSecret = "client_secret "末尾有空格,和Google控制台配置不匹配
  3. 未捕获具体错误响应:当前MakeWebRequest仅返回WebException的ToString,无法获取Google返回的详细错误信息
  4. 使用过时的用户信息端点: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()}";
    }
}

四、关键注意事项

  1. 重定向URI完全匹配:Google控制台配置的重定向URI必须和代码里的完全一致(包括HTTP/HTTPS、结尾斜杠等)
  2. ClientSecret无多余字符:检查配置里的ClientSecret是否有空格或换行符
  3. 异步操作规范:WebForms中必须在页面指令开启Async="true",且用async/await处理所有异步方法
  4. 权限范围:确保申请的Scopes包含email、profile等需要的权限

内容的提问来源于stack exchange,提问作者DKT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 22:37:12