.NET MAUI中为Azure B2C多用户流配置多IPublicClientApplication实例的正确方法
问题原因
.NET MAUI使用的Microsoft.Extensions.DependencyInjection容器中,同一服务类型的多次注册会被最后一次注册覆盖。你两次注册IPublicClientApplication类型,最终所有依赖该类型的服务都会拿到最后注册的密码重置实例,这就是两个认证服务共用同一实例的根本原因。
解决方案
下面提供三种可行的解决方式,按简洁度和推荐度排序:
方案1:直接在服务注册时传入实例(最简洁)
不需要修改现有认证服务的代码,直接在注册服务时创建对应IPublicClientApplication实例并传入构造函数:
// 创建登录策略对应的实例 var signInApp = PublicClientApplicationBuilder .Create(Constants.ClientId) .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups) .WithRedirectUri($"msal{Constants.ClientId}://auth") .WithB2CAuthority(Constants.AuthoritySignIn) .Build(); // 注册登录服务,直接传入实例 mauiAppBuilder.Services.AddScoped<IAuthService>(sp => new AuthServiceB2CSignInSignUp(signInApp)); // 创建密码重置策略对应的实例 var resetPasswordApp = PublicClientApplicationBuilder .Create(Constants.ClientId) .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups) .WithRedirectUri($"msal{Constants.ClientId}://auth") .WithB2CAuthority(Constants.AuthorityPasswordReset) .Build(); // 注册密码重置服务,直接传入实例 mauiAppBuilder.Services.AddScoped<IAuthServiceB2CResetPassword>(sp => new AuthServiceB2CResetPassword(resetPasswordApp));
方案2:使用Keyed Services(.NET 6+官方推荐)
利用.NET 6引入的Keyed Services特性,为不同实例指定唯一标识,在注入时通过标识区分:
步骤1:修改服务注册
// 注册登录用的IPublicClientApplication,指定键为"SignIn" mauiAppBuilder.Services.AddKeyedScoped<IPublicClientApplication>("SignIn", sp => { return PublicClientApplicationBuilder .Create(Constants.ClientId) .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups) .WithRedirectUri($"msal{Constants.ClientId}://auth") .WithB2CAuthority(Constants.AuthoritySignIn) .Build(); }); // 注册密码重置用的IPublicClientApplication,指定键为"ResetPassword" mauiAppBuilder.Services.AddKeyedScoped<IPublicClientApplication>("ResetPassword", sp => { return PublicClientApplicationBuilder .Create(Constants.ClientId) .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups) .WithRedirectUri($"msal{Constants.ClientId}://auth") .WithB2CAuthority(Constants.AuthorityPasswordReset) .Build(); }); // 正常注册认证服务 mauiAppBuilder.Services.AddScoped<IAuthService, AuthServiceB2CSignInSignUp>(); mauiAppBuilder.Services.AddScoped<IAuthServiceB2CResetPassword, AuthServiceB2CResetPassword>();
步骤2:修改认证服务构造函数
通过[FromKeyedServices]属性指定要注入的实例标识:
// 登录服务构造函数 public class AuthServiceB2CSignInSignUp : IAuthService { private readonly IPublicClientApplication _signInApp; public AuthServiceB2CSignInSignUp([FromKeyedServices("SignIn")] IPublicClientApplication signInApp) { _signInApp = signInApp; } // 原有方法保持不变 } // 密码重置服务构造函数 public class AuthServiceB2CResetPassword : IAuthServiceB2CResetPassword { private readonly IPublicClientApplication _resetApp; public AuthServiceB2CResetPassword([FromKeyedServices("ResetPassword")] IPublicClientApplication resetApp) { _resetApp = resetApp; } // 原有方法保持不变 }
方案3:创建包装类区分实例(兼容旧版本)
如果需要兼容.NET 6以下版本,可以创建专属包装类来区分不同的IPublicClientApplication实例:
步骤1:创建包装类
// 登录策略专用包装类 public class SignInPublicClientApplication : IPublicClientApplication { private readonly IPublicClientApplication _innerApp; public SignInPublicClientApplication(IPublicClientApplication innerApp) { _innerApp = innerApp; } // 实现IPublicClientApplication的所有接口方法,直接委托给_innerApp public Task<IEnumerable<IAccount>> GetAccountsAsync(string? policy = null) => _innerApp.GetAccountsAsync(policy); public AcquireTokenInteractiveParameterBuilder AcquireTokenInteractive(IEnumerable<string> scopes) => _innerApp.AcquireTokenInteractive(scopes); // 其他接口方法同理,全部委托给_innerApp } // 密码重置策略专用包装类 public class ResetPasswordPublicClientApplication : IPublicClientApplication { private readonly IPublicClientApplication _innerApp; public ResetPasswordPublicClientApplication(IPublicClientApplication innerApp) { _innerApp = innerApp; } // 同样实现所有IPublicClientApplication接口方法,委托给_innerApp }
步骤2:修改服务注册
// 注册登录用的包装实例 mauiAppBuilder.Services.AddScoped<SignInPublicClientApplication>(sp => { var app = PublicClientApplicationBuilder .Create(Constants.ClientId) .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups) .WithRedirectUri($"msal{Constants.ClientId}://auth") .WithB2CAuthority(Constants.AuthoritySignIn) .Build(); return new SignInPublicClientApplication(app); }); // 注册密码重置用的包装实例 mauiAppBuilder.Services.AddScoped<ResetPasswordPublicClientApplication>(sp => { var app = PublicClientApplicationBuilder .Create(Constants.ClientId) .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups) .WithRedirectUri($"msal{Constants.ClientId}://auth") .WithB2CAuthority(Constants.AuthorityPasswordReset) .Build(); return new ResetPasswordPublicClientApplication(app); }); // 注册认证服务 mauiAppBuilder.Services.AddScoped<IAuthService, AuthServiceB2CSignInSignUp>(); mauiAppBuilder.Services.AddScoped<IAuthServiceB2CResetPassword, AuthServiceB2CResetPassword>();
步骤3:修改认证服务构造函数
注入对应的包装类:
// 登录服务构造函数 public class AuthServiceB2CSignInSignUp : IAuthService { private readonly IPublicClientApplication _signInApp; public AuthServiceB2CSignInSignUp(SignInPublicClientApplication signInApp) { _signInApp = signInApp; } // 原有方法保持不变 } // 密码重置服务构造函数 public class AuthServiceB2CResetPassword : IAuthServiceB2CResetPassword { private readonly IPublicClientApplication _resetApp; public AuthServiceB2CResetPassword(ResetPasswordPublicClientApplication resetApp) { _resetApp = resetApp; } // 原有方法保持不变 }
内容的提问来源于stack exchange,提问作者Vladimir
相关产品推荐
相关产品推荐

