ASP 样板中 LDAP 实现的 InvalidCastException

InvalidCastException on an LDAP implementation in ASP Boilerplate

大家好,最近不得不为我们的应用程序实施 Active Directory,过去几天我们在尝试登录该站点时遇到了 InvalidCastException 我一直在绞尽脑汁想弄清楚是什么导致此错误。代码似乎 运行 没问题,因为加载时没有错误

LdapAuthentication.cs:

 public abstract class LdapAuthenticationSource<TTenant, TUser> : DefaultExternalAuthenticationSource<TTenant, TUser>, ITransientDependency
        where TTenant : AbpTenant<TUser>
        where TUser : AbpUserBase, new()
    {

        /// <summary>
        /// LDAP
        /// </summary>
        public const string SourceName = "LDAP";

        public override string Name
        {
            get { return SourceName; }
        }

        private readonly ILdapSettings _settings;
        private readonly IAbpZeroLdapModuleConfig _ldapModuleConfig;

        protected LdapAuthenticationSource(ILdapSettings settings, IAbpZeroLdapModuleConfig ldapModuleConfig)
        {
            _settings = settings;
            _ldapModuleConfig = ldapModuleConfig;
        }

        /// <inheritdoc/>
        public override async Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, TTenant tenant)
        {
            if (!_ldapModuleConfig.IsEnabled || !(await _settings.GetIsEnabled(GetIdOrNull(tenant))))
            {
                return false;
            }

            using (var principalContext = await CreatePrincipalContext(tenant))
            {
                return ValidateCredentials(principalContext, userNameOrEmailAddress, plainPassword);
            }
        }

        /// <inheritdoc/>
        public async override Task<TUser> CreateUserAsync(string userNameOrEmailAddress, TTenant tenant)
        {
            await CheckIsEnabled(tenant);

            var user = await base.CreateUserAsync(userNameOrEmailAddress, tenant);

            using (var principalContext = await CreatePrincipalContext(tenant))
            {
                var userPrincipal = UserPrincipal.FindByIdentity(principalContext, userNameOrEmailAddress);

                if (userPrincipal == null)
                {
                    throw new AbpException("Unknown LDAP user: " + userNameOrEmailAddress);
                }

                UpdateUserFromPrincipal(user, userPrincipal);

                user.IsEmailConfirmed = true;
                user.IsActive = true;

                return user;
            }
        }

        public async override Task UpdateUserAsync(TUser user, TTenant tenant)
        {
            await CheckIsEnabled(tenant);

            await base.UpdateUserAsync(user, tenant);

            using (var principalContext = await CreatePrincipalContext(tenant))
            {
                var userPrincipal = UserPrincipal.FindByIdentity(principalContext, user.UserName);

                if (userPrincipal == null)
                {
                    throw new AbpException("Unknown LDAP user: " + user.UserName);
                }

                UpdateUserFromPrincipal(user, userPrincipal);
            }
        }

        protected virtual bool ValidateCredentials(PrincipalContext principalContext, string userNameOrEmailAddress, string plainPassword)
        {
            return principalContext.ValidateCredentials(userNameOrEmailAddress, plainPassword, ContextOptions.Negotiate);
        }

        protected virtual void UpdateUserFromPrincipal(TUser user, UserPrincipal userPrincipal)
        {
            user.UserName = userPrincipal.SamAccountName;
            user.Name = userPrincipal.GivenName;
            user.Surname = userPrincipal.Surname;
            user.EmailAddress = userPrincipal.EmailAddress;

            if (userPrincipal.Enabled.HasValue)
            {
                user.IsActive = userPrincipal.Enabled.Value;
            }
        }

        protected virtual async Task<PrincipalContext> CreatePrincipalContext(TTenant tenant)
        {
            var tenantId = GetIdOrNull(tenant);

            return new PrincipalContext(
                await _settings.GetContextType(tenantId),
                ConvertToNullIfEmpty(await _settings.GetDomain(tenantId)),
                ConvertToNullIfEmpty(await _settings.GetContainer(tenantId)),
                ConvertToNullIfEmpty(await _settings.GetUserName(tenantId)),
                ConvertToNullIfEmpty(await _settings.GetPassword(tenantId))
                );
        }

        private async Task CheckIsEnabled(TTenant tenant)
        {
            if (!_ldapModuleConfig.IsEnabled)
            {
                throw new AbpException("Ldap Authentication module is disabled globally!");
            }

            var tenantId = GetIdOrNull(tenant);
            if (!await _settings.GetIsEnabled(tenantId))
            {
                throw new AbpException("Ldap Authentication is disabled for given tenant (id:" + tenantId + ")! You can enable it by setting '" + LdapSettingNames.IsEnabled + "' to true");
            }
        }

        private static int? GetIdOrNull(TTenant tenant)
        {
            return tenant == null
                ? (int?)null
                : tenant.Id;
        }

        private static string ConvertToNullIfEmpty(string str)
        {
            return str.IsNullOrWhiteSpace()
                ? null
                : str;
        }

    }
}

LdapSettings.cs

public class LdapSettings: ILdapSettings, ITransientDependency
    {

        protected ISettingManager SettingManager { get; }

        public LdapSettings(ISettingManager settingManager)
        {
            SettingManager = settingManager;
        }

        public virtual Task<bool> GetIsEnabled(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync<bool>(AppSettingNames.IsEnabled, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync<bool>(AppSettingNames.IsEnabled);
        }

        public virtual async Task<ContextType> GetContextType(int? tenantId)
        {
            return tenantId.HasValue
                ? (await SettingManager.GetSettingValueForTenantAsync(AppSettingNames.ContextType, tenantId.Value)).ToEnum<ContextType>()
                : (await SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.ContextType)).ToEnum<ContextType>();
        }

        public virtual Task<string> GetContainer(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync(AppSettingNames.Container, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.Container);
        }

        public virtual Task<string> GetDomain(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync(AppSettingNames.Domain, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.Domain);
        }

        public virtual Task<string> GetUserName(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync(AppSettingNames.UserName, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.UserName);
        }

        public virtual Task<string> GetPassword(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync(AppSettingNames.Password, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.Password);
        }
    }
}

CoreModule.cs

    [DependsOn(typeof(AbpZeroLdapModule))]
    public class TestApp2020CoreModule : AbpModule
    {
        public override void PreInitialize()
        {

            Configuration.Auditing.IsEnabledForAnonymousUsers = true;

            // Declare entity types
            Configuration.Modules.Zero().EntityTypes.Tenant = typeof(Tenant);
            Configuration.Modules.Zero().EntityTypes.Role = typeof(Role);
            Configuration.Modules.Zero().EntityTypes.User = typeof(User);

            TestApp2020LocalizationConfigurer.Configure(Configuration.Localization);

            // Enable this line to create a multi-tenant application.
            Configuration.MultiTenancy.IsEnabled = TestApp2020Consts.MultiTenancyEnabled;

            // IocManager.Register<ILdapSettings, MyLdapSettings>(); //change default setting source
            IocManager.Register<ILdapSettings, LdapSettings>();
            Configuration.Modules.ZeroLdap().Enable(typeof(LdapSettings));
            // Configure roles
            AppRoleConfig.Configure(Configuration.Modules.Zero().RoleManagement);

            Configuration.Settings.Providers.Add<AppSettingProvider>();
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(typeof(TestApp2020CoreModule).GetAssembly());
        }

        public override void PostInitialize()
        {
            IocManager.Resolve<AppTimes>().StartupTime = Clock.Now;
            SettingManager settingsManager = IocManager.Resolve<SettingManager>();
            settingsManager.ChangeSettingForApplication(AppSettingNames.IsEnabled, "true");
        }
    }
}

应用程序已加载,但此处的错误阻止登录

这就是日志中显示的内容

如有任何帮助,我们将不胜感激。

tl;博士;

你的问题在CoreModule.cs

Configuration.Modules.ZeroLdap().Enable(typeof(LdapSettings));

根据 the docsEnable 方法将 auth 来源类型作为参数,但您已经传递了设置类型。改为使用 LdapAuthenticationSource

你是怎么想出来的

错误消息说从 LdapSettingsIExternalAuthenticationSource 的转换失败。这很奇怪,因为您的代码没有理由尝试在这些类型之间进行转换!

如果向下看堆栈,您会发现错误发生在 TokenAuthControllerAuthenticate / GetLoginResultAsync 方法中。您可以检查该方法中的代码,您可能找不到任何直接提及 LdapSettingsIExternalAuthenticationSource 的地方。但是,您会发现对 ApbLoginManger.LoginAsync 的调用。按照那个备份堆栈,你可以看到ApbLoginManager使用IoC来解析auth源,并且在IoC的ResolveAsDisposable方法中抛出异常!

这里有点棘手。该错误是 呈现 本身深入 ABP 和 IoC 框架。可能是其中一个框架中存在一个不明显的错误导致了问题,但更有可能是 配置错误 。这意味着下一步是查看您的配置代码,以查找您可能告诉 IoC 框架使用 LdapSettings 作为 IExternalAuthenticationSource.

的任何地方

所有配置都在 CoreModule.cs 文件中,让我们看看那里。您有电话打给

IocManager.Register<ILdapSettings, LdapSettings>();

这似乎正确地为 ILdapSettings 注册了 LdapSettings。对 IocManager 的唯一其他调用是 Initialize 方法中对 IocManager.RegisterAssemblyByConvention 的标准调用。那里没有明显的错误配置。然而,有一个使用 typeof(LdapSettings) 作为参数的调用。

Configuration.Modules.ZeroLdap().Enable(typeof(LdapSettings));

从方法调用中看不出该参数的用途,LdapSettings 绝对是正确参数的合理可能性。但是,有两个充分的理由进一步研究这种方法。

  1. 因为参数是 Type,所以编译时不会检查我们是否传递了合适的类型。
  2. LdapSettings 是实际异常的一部分,因此任何使用它的方法都是可疑的

这将我们带到 the documentation 我们看到问题的地方。我们需要传递授权源,而不是设置。

为什么代码“看起来 运行 没问题”

配置使用了 Type 参数而不是泛型。这意味着没有编译时检查您是否传递了有效类型(如上所述)。程序编译 运行 正常 直到您尝试使用配置错误的代码 。在这种情况下,在您尝试登录之前不会使用错误配置,这会触发 IoC 解析器,它访问配置并抛出错误。