.NET MAUI、Azure B2C、具有多个命名IPublicClientApplication的不同用户流

ippsafx7  于 2023-10-22  发布在  .NET
关注(0)|答案(2)|浏览(92)

我正在使用一个使用Azure AD B2C进行身份验证的.NET MAUI应用程序。
我的应用程序中有多个Azure AD B2C策略(正常运行的注册并希望添加密码重置策略),并且我需要为不同的策略使用不同的IPublicClientApplication示例(因为它们使用不同的用户流)。
我尝试了几种方法来配置依赖注入,但遇到了两个服务最终使用同一个IPublicClientApplication示例的问题。
以下是工作正常的登录服务:

public class AuthServiceB2CSignInSignUp : IAuthService
{
    private readonly IPublicClientApplication _signInApp;

    public AuthServiceB2CSignInSignUp(IPublicClientApplication signInApp)
    {
        _signInApp = signInApp;
    }
    public Task<AuthenticationResult?> SignInInteractively(CancellationToken cancellationToken)
    {
        return _signInApp
               .AcquireTokenInteractive(Constants.Scopes)
               .ExecuteAsync(cancellationToken);
    }

    public async Task<AuthenticationResult?> AcquireTokenSilent(CancellationToken cancellationToken)
    {
        try
        {
            var accounts = await _signInApp.GetAccountsAsync(Constants.SignInPolicy);
            var firstAccount = accounts.FirstOrDefault();
            if (firstAccount is null)
            {
                return null;
            }

            return await _signInApp.AcquireTokenSilent(Constants.Scopes, firstAccount)
                                             .ExecuteAsync(cancellationToken);
        }
        catch (MsalUiRequiredException)
        {
            return null;
        }
    }

    public async Task LogoutAsync(CancellationToken cancellationToken)
    {
        var accounts = await _signInApp.GetAccountsAsync();
        foreach (var account in accounts)
        {
            await _signInApp.RemoveAsync(account);
        }
    }
}

下面是密码重置,如果我自己运行它也可以正常工作:

public class AuthServiceB2CResetPassword : IAuthServiceB2CResetPassword
{
    private readonly IPublicClientApplication _resetApp;
    public AuthServiceB2CResetPassword(IPublicClientApplication resetApp)
    {
         _resetApp = resetApp;
    }
    public Task<AuthenticationResult?> SignInInteractively(CancellationToken cancellationToken)
    {
        return _resetApp
               .AcquireTokenInteractive(Constants.Scopes)
               .ExecuteAsync(cancellationToken);
    }

    public async Task<AuthenticationResult?> AcquireTokenSilent(CancellationToken cancellationToken)
    {
        try
        {
            var accounts = await _resetApp.GetAccountsAsync(Constants.SignInPolicy);
            var firstAccount = accounts.FirstOrDefault();
            if (firstAccount is null)
            {
                return null;
            }

            return await _resetApp.AcquireTokenSilent(Constants.Scopes, firstAccount)
                                             .ExecuteAsync(cancellationToken);
        }
        catch (MsalUiRequiredException)
        {
            return null;
        }
    }
}

这是我注册它们的方式:

mauiAppBuilder.Services.AddScoped<IAuthService, AuthServiceB2CSignInSignUp>();
mauiAppBuilder.Services.AddScoped<IPublicClientApplication>(sp =>
{
    var app = PublicClientApplicationBuilder
        .Create(Constants.ClientId)
        .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups)
        .WithRedirectUri($"msal{Constants.ClientId}://auth")
         .WithB2CAuthority(Constants.AuthoritySignIn)
        .Build();
    return app;
});

mauiAppBuilder.Services.AddScoped<IAuthServiceB2CResetPassword, AuthServiceB2CResetPassword>();
mauiAppBuilder.Services.AddScoped<IPublicClientApplication>(sp =>
{
    var passwordResetApp = PublicClientApplicationBuilder
        .Create(Constants.ClientId)
        .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups)
        .WithRedirectUri($"msal{Constants.ClientId}://auth")
        .WithB2CAuthority(Constants.AuthorityPasswordReset)
        .Build();

    return passwordResetApp;
});

在我的登录视图模型中,我将它们注入到构造函数中:

private readonly IAuthService _signupService;
private readonly IAuthServiceB2CResetPassword _passwordResetService;

public LoginViewModel(IAuthService signupService, IAuthServiceB2CResetPassword passwordResetService)
{
    _signupService = signupService;     
    _passwordResetService = passwordResetService;
}

//do something here

但是,无论IPublicClientApplication的最后一个注册示例是什么,它总是覆盖另一个注册示例,因为PublicClientApplication来自Microsoft.Identity.Client,我真的不明白应该采取什么方法来拥有两个不同的IPublicClientApplication对象,一个用于密码重置,一个用于登录。
我尝试更改顺序,但也没有帮助,尽管配置了IPublicClientApplication的命名示例,但两个服务(_signupService_passwordResetService)最终使用相同的IPublicClientApplication示例。
我已经尝试命名注册,但它似乎不像预期的那样工作。.NET MAUI的依赖注入系统中是否有限制阻止了这种设置,或者我的配置中缺少了什么?
在.NET MAUI应用程序中,为不同服务的多个命名IPublicClientApplication示例配置依赖注入的正确方法是什么?我也试着找到一些官方的Azure B2C MAUI示例,但Azure-Samples中的示例甚至没有实现密码重置,只有登录注册。我想同时实施这两项措施。

0qx6xfy6

0qx6xfy61#

这不是MAUI特有的,这只是Microsoft-provided .NET dependency injection system的工作方式。
在Microsoft系统中,当你为同一个接口注册额外的实现时(有一些警告),它会将所有的实现添加到服务集合中 *,但是 * 只有当你将它们注入为IEnumerable<T>时,它才会返回这些实现。如果你只注入一个接口,那么它将永远是最后一个注册的实现。
举例来说:

// during service registration
builder.Services.AddTransient<IMyDependency, MyDependencyA>();
builder.Services.AddTransient<IMyDependency, MyDependencyB>();

// when injecting multiple implementations into a service
public class MyServiceWithMultipleDependencies
{
    public MyServiceWithMultipleDependencies(IEnumerable<IMyDependency> dependencies)
    {
        /*
         * dependencies will contain two items: MyDependencyA and MyDependencyB
         * the implementations will be enumerated in the order they were registered
         */
    }
}

// when injecting a single implementations into a service
public class MyServiceWithOneDependency
{
    public MyServiceWithOneDependency(IMyDependency dependency)
    {
        // dependency will be MyDependencyB, the last implementation registered for IMyDependency
    }
}

// still counts as injecting a single implementations into a service
public class MyServiceWithOneDependency
{
    public MyServiceWithOneDependency(IMyDependency dependencyA, IMyDependency dependency2)
    {
        /* dependencyA will be MyDependencyB, the last implementation registered for IMyDependency
         * dependency2 will be MyDependencyB, the last implementation registered for IMyDependency
         *
         * The names of the parameters have no effect on the dependencies 
         * injected, they're just labels that make sense in the context 
         * of the service the dependency is injected into.
         */
    }
}

如果你需要以你描述的方式注入一个特定的实现,那么你可以使用微软的DI系统,比如this Stack Overflow answer,或者你可以集成更高级的DI容器,比如Autofac,它支持命名服务和the factory pattern

cs7cruho

cs7cruho2#

根据@Dave D的建议,我使用工厂模式进行了重构(该帖子的链接也非常有用),我将把它放在这里,以防有人想要重用。

public class PublicClientApplicationFactory
    {
        private readonly Dictionary<string, IPublicClientApplication> _configuredApps = new Dictionary<string, IPublicClientApplication>();

        public IPublicClientApplication GetClientApplication(string configurationName)
        {
            // Create and configure the app based on the specified configuration
            var app = CreateAndConfigureClientApplication(configurationName);
            _configuredApps.Add(configurationName, app);

            return app;
        }

        private IPublicClientApplication CreateAndConfigureClientApplication(string configurationName)
        {
            switch (configurationName)
            {
                case "SignIn":
                    return PublicClientApplicationBuilder
                        .Create(Constants.ClientId)
                        .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups)
                        .WithRedirectUri($"msal{Constants.ClientId}://auth")
                        .WithB2CAuthority(Constants.AuthoritySignIn)
                        .Build();

                case "PasswordReset":
                    return PublicClientApplicationBuilder
                        .Create(Constants.ClientId)
                        .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups)
                        .WithRedirectUri($"msal{Constants.ClientId}://auth")
                        .WithB2CAuthority(Constants.AuthorityPasswordReset)
                        .Build();

                default:
                    throw new InvalidOperationException($"Unknown configuration: {configurationName}");
            }
        }
    }

每个用户流的单独类

public class AuthServiceB2CSignInSignUp : IAuthService
    {
        private readonly IPublicClientApplication _signInApp;

        public AuthServiceB2CSignInSignUp(PublicClientApplicationFactory appFactory)
        {
            _signInApp = appFactory.GetClientApplication("SignIn");
        }
//methods here

密码重置

public class AuthServiceB2CResetPassword : IAuthServiceB2CResetPassword
    {
        private readonly IPublicClientApplication _resetApp;

        public AuthServiceB2CResetPassword(PublicClientApplicationFactory appFactory)
        {
            _resetApp = appFactory.GetClientApplication("PasswordReset");
        }

这样的注册

mauiAppBuilder.Services.AddSingleton<PublicClientApplicationFactory>();
        mauiAppBuilder.Services.AddScoped<IAuthService, AuthServiceB2CSignInSignUp>();
        mauiAppBuilder.Services.AddScoped<IAuthServiceB2CResetPassword, AuthServiceB2CResetPassword>();

然后,在注入之后,可以单独访问每个服务

相关问题