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

.NET 7 MAUI对接本地Duende IdentityServer测试认证问题求助

.NET 7 MAUI对接Duende IdentityServer本地测试问题解决方案

问题概述

开发.NET 7 MAUI应用对接本地Duende IdentityServer(v6.2.3)时,本地测试遇到三个核心问题:

  • 使用ngrok转发IDP时,发现文档端点仍指向localhost,与ngrok域名不匹配,触发主机错误:System.InvalidOperationException: 'Error loading discovery document: Endpoint is on a different host than authority: https://localhost:5001/.well-known/openid-configuration/jwks'
  • Android模拟器访问https://10.0.2.2时,出现证书信任错误:System.InvalidOperationException: 'Error loading discovery document: Error connecting to https://10.0.2.2/.well-known/openid-configuration. java.security.cert.CertPathValidatorException: Trust anchor for certification path not found..'
  • IDP切换为HTTP模式后,客户端提示HTTPS强制要求:System.InvalidOperationException: 'Error loading discovery document: Error connecting to http://10.0.2.2/.well-known/openid-configuration. HTTPS required.'

关键代码参考

IDP Program.cs

builder.Services.AddIdentityServer(options =>
        {              
            options.EmitStaticAudienceClaim = true;
        })
        .AddInMemoryIdentityResources(Config.IdentityResources)
        .AddInMemoryApiScopes(Config.ApiScopes)
        .AddInMemoryClients(Config.Clients)
        .AddTestUsers(TestUsers.Users);

IDP Config类

using Duende.IdentityServer;
using Duende.IdentityServer.Models;

namespace MyApp.IDP;

public static class Config
{
    public static IEnumerable<IdentityResource> IdentityResources =>
        new IdentityResource[]
        { 
            new IdentityResources.OpenId(),
            new IdentityResources.Profile()
        };

    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
            { };

    public static IEnumerable<Client> Clients =>
        new Client[] 
            { 
                new Client()
                {
                    ClientName = "My App Mobile",
                    ClientId = "myappmobile.client",
                    AllowedGrantTypes = GrantTypes.Code,
                    RedirectUris = {
                        "myapp://callback" 
                    },
                    PostLogoutRedirectUris = { 
                        "myapp://callback"
                    },
                    AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile                       
                    }
                }
            };
}

MAUI客户端配置

var options = new OidcClientOptions
{       
    Authority = "https://10.0.2.2",
    ClientId = "myappmobile.client",        
    RedirectUri = "myapp://callback",
    Browser = new MauiAuthenticationBrowser()
};

builder.Services.AddSingleton(new OidcClient(options));

MauiAuthenticationBrowser类

using IdentityModel.Client;
using IdentityModel.OidcClient.Browser;

namespace MyFirstAuth;

public class MauiAuthenticationBrowser : IdentityModel.OidcClient.Browser.IBrowser
{
    public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
    {
        try
        {
            var result = await WebAuthenticator.Default.AuthenticateAsync(
                new Uri(options.StartUrl),
                new Uri(options.EndUrl));

            var url = new RequestUrl("myapp://callback")
                .Create(new Parameters(result.Properties));

            return new BrowserResult
            {
                Response = url,
                ResultType = BrowserResultType.Success
            };
        }
        catch (TaskCanceledException)
        {
            return new BrowserResult
            {
                ResultType = BrowserResultType.UserCancel
            };
        }
    }
}

MAUI MainPage代码

using IdentityModel.OidcClient;

namespace MyFirstAuth;
public partial class MainPage
{
    private readonly OidcClient _client;

    public MainPage(OidcClient client)
    {
        InitializeComponent();
        _client = client;
    }

    private async void OnLoginClicked(object sender, EventArgs e)
    {
        var result = await _client.LoginAsync();

        if (result.IsError)
        {
            editor.Text = result.Error;
            return;
        }

        editor.Text = "Success!";
    }
}

针对性解决方案

1. 解决ngrok主机不匹配问题

IdentityServer默认根据当前请求主机生成发现文档端点,ngrok转发时需强制指定PublicOrigin确保端点域名一致:

// 修改IDP Program.cs
builder.Services.AddIdentityServer(options =>
{              
    options.EmitStaticAudienceClaim = true;
    // 替换为你的ngrok域名,例如https://abc123.ngrok.io
    options.PublicOrigin = "https://your-ngrok-domain.ngrok.io";
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);

// 添加转发头中间件,确保IdentityServer识别外部请求
app.UseForwardedHeaders();

同时更新MAUI客户端的Authority为ngrok域名。

2. 解决Android本地证书信任问题

方案A:让Android信任开发证书(推荐测试用)

  1. 导出本地开发证书:从%USERPROFILE%\.aspnet\https目录找到IDP项目的.pfx文件,导出为.crt格式。
  2. 将.crt文件放入MAUI项目的Platforms/Android/Resources/raw目录(无则新建)。
  3. 在Platforms/Android/MainApplication.cs中添加证书信任逻辑:
using Android.App;
using Android.Runtime;
using System.Security.Cryptography.X509Certificates;
using Android.Net.Http;

namespace YourAppNamespace;

[Application]
public class MainApplication : MauiApplication
{
    public MainApplication(IntPtr handle, JniHandleOwnership ownership)
        : base(handle, ownership)
    {
    }

    protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();

    public override void OnCreate()
    {
        base.OnCreate();
        InstallLocalCertificate();
    }

    private void InstallLocalCertificate()
    {
        try
        {
            // 替换为你的证书文件名(不含.crt后缀)
            using var stream = Resources.OpenRawResource(Resource.Raw.your_local_cert);
            var certFactory = X509CertificateFactory.GetInstance("X.509");
            var cert = certFactory.GenerateCertificate(stream);
            
            var keyStore = KeyStore.GetInstance(KeyStore.DefaultType);
            keyStore.Load(null);
            keyStore.SetCertificateEntry("local_dev_cert", cert);
            
            var trustManagerFactory = TrustManagerFactory.GetInstance(TrustManagerFactory.DefaultAlgorithm);
            trustManagerFactory.Init(keyStore);
            
            var sslContext = SSLContext.GetInstance("TLS");
            sslContext.Init(null, trustManagerFactory.GetTrustManagers(), new Java.Security.SecureRandom());
            
            HttpsURLConnection.DefaultSSLSocketFactory = sslContext.SocketFactory;
            HttpsURLConnection.DefaultHostnameVerifier = new AcceptAllHostnameVerifier();
        }
        catch (Exception ex)
        {
            // 记录错误日志
        }
    }
}

public class AcceptAllHostnameVerifier : Java.Lang.Object, IHostnameVerifier
{
    public bool Verify(string hostname, SSLSession session) => true;
}

方案B:绕过证书检查(仅临时测试)

为OidcClient配置自定义HttpHandler跳过证书验证:

// 修改MAUI客户端配置
var handler = new HttpClientHandler
{
    ServerCertificateCustomValidationCallback = (msg, cert, chain, errors) => true
};

var options = new OidcClientOptions
{       
    Authority = "https://10.0.2.2",
    ClientId = "myappmobile.client",        
    RedirectUri = "myapp://callback",
    Browser = new MauiAuthenticationBrowser(),
    BackchannelHandler = handler
};

builder.Services.AddSingleton(new OidcClient(options));

⚠️ 此方法仅适用于本地测试,生产环境绝对禁止使用。

3. 解决HTTP模式下HTTPS强制要求问题

要在测试环境启用HTTP,需同时修改IDP和客户端配置:

IDP端修改(Program.cs)

builder.Services.AddIdentityServer(options =>
{              
    options.EmitStaticAudienceClaim = true;
    options.IssuerUri = "http://10.0.2.2:5000";
    options.KeyManagement.Enabled = false; // 禁用密钥管理,避免HTTPS依赖
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);

// 监听HTTP端口
app.Run("http://0.0.0.0:5000");

MAUI客户端修改

var options = new OidcClientOptions
{       
    Authority = "http://10.0.2.2:5000",
    ClientId = "myappmobile.client",        
    RedirectUri = "myapp://callback",
    Browser = new MauiAuthenticationBrowser(),
    Policy = new OidcClientPolicy
    {
        AllowUnsafeNonHttps = true // 允许HTTP权威,仅测试用
    }
};

⚠️ 生产环境必须使用HTTPS,此配置仅用于本地测试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 12:22:06