ASP Boilerplate中LDAP实现上的InvalidCastException [英] InvalidCastException on an LDAP implementation in ASP Boilerplate

查看:81
本文介绍了ASP Boilerplate中LDAP实现上的InvalidCastException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一切顺利,最近不得不为我们的应用程序实现Active Directory,并且在过去几天尝试登录该站点时遇到了InvalidCastException,我一直不知所措,试图找出导致问题的原因.这个错误.该代码确实运行正常,因为加载时没有错误

Good day all, recently had to implement an Active Directory for our application and we have come across an InvalidCastException when trying to log into the site for the past few days I have been at our wits end trying to figure out what is causing this error. The code does seems to run fine as there are no errors when loading

LdapAuthentication.cs:

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

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

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");
        }
    }
}

应用程序已加载,但此处出现错误,导致无法登录

The application loads but the an error here prevents logging in

这就是日志中显示的内容

And this is what is showing in the logs

任何帮助将不胜感激.

推荐答案

tl; dr;

您的问题出在CoreModule.cs

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

根据 docs Enable方法采用授权来源类型作为参数,但是您已经传递了设置类型.更改为使用LdapAuthenticationSource.

According to the docs the Enable method takes an auth source type as a parameter, but you've passed a settings type. Change it to use LdapAuthenticationSource instead.

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

The error message says there was a failed cast from LdapSettings to IExternalAuthenticationSource. That's strange because there's no reason your code should be trying to cast between those types!

如果您往下看堆栈,您会发现错误发生在您的TokenAuthControllerAuthenticate/GetLoginResultAsync方法中.您可以检查该方法中的代码,可能找不到直接引用LdapSettingsIExternalAuthenticationSource的代码.但是,您会找到对ApbLoginManger.LoginAsync的呼叫.然后将其备份到堆栈中,您可以看到ApbLoginManager使用IoC解析身份验证源,并且在IoC的ResolveAsDisposable方法中引发了异常!

If you look down the stack, you can see the error is happening inside your TokenAuthController's Authenticate / GetLoginResultAsync method. You could check the code in that method, you probably won't find any direct mention of either LdapSettings or IExternalAuthenticationSource. You will however find a call to ApbLoginManger.LoginAsync. Follow that back up the stack and you can see ApbLoginManager uses IoC to resolve an auth source, and the exception is thrown in the ResolveAsDisposable method of IoC!

这里有点棘手.该错误是在ABP和IoC框架内部展示自身.其中一个框架中可能有一个晦涩的错误导致了问题,但更可能是配置错误.这意味着下一步是在可能告诉IoC框架将LdapSettings用作IExternalAuthenticationSource的任何地方浏览配置代码.

It gets a bit trickier here. The bug is presenting itself deep inside ABP and the IoC framework. It's possible there's an obscure bug in one of those frameworks causing the problem, but it's much more likely to be a configuration error. That means the next step is to look through your configuration code for anywhere you may have told the IoC framework to use LdapSettings for an IExternalAuthenticationSource.

所有配置都发生在CoreModule.cs文件中,所以让我们看一下.您有一个电话

All the config happens in the CoreModule.cs file, so let's look there. You have a call to

IocManager.Register<ILdapSettings, LdapSettings>();

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

which seems to properly register LdapSettings for ILdapSettings. The only other call to IocManager is the standard call to IocManager.RegisterAssemblyByConvention in the Initialize method. No obvious misconfiguration there. There is however a call that uses typeof(LdapSettings) as a parameter.

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

从方法调用中并不清楚该参数的用途,并且LdapSettings绝对是正确参数的合理可能性.但是,有两个很好的理由来进一步研究此方法.

It's not obvious from the method call what that parameter is for, and LdapSettings is definitely a reasonable possibility for the correct parameter. However, there are two good reasons to look into this method further.

  1. 因为参数是Type,所以如果我们传递了适当的类型,则不会进行编译时检查.
  2. LdapSettings是实际异常的一部分,因此使用该异常的任何方法都是可疑的
  1. Because the parameter is a Type, there won't be compile time checking if we've passed an appropriate type.
  2. LdapSettings is part of the actual exception so any method that uses it is suspect

这将我们带到文档,在那里我们看到了问题.我们需要传递身份验证源,而不是设置.

That brings us to the documentation where we see the problem. We need to pass the auth source, not the settings.

配置使用Type参数代替泛型.这意味着没有编译时检查是否传递了有效的类型(如上所述).该程序可以编译并正常运行,直到您尝试使用配置错误的代码为止.在这种情况下,除非您尝试登录,否则不会使用错误配置,这会触发IoC解析器,该解析器将访问配置并引发错误.

The configuration used a Type parameter instead of generics. That means there's no compile time checking if you've passed a valid type (as mentioned above). The program compile and run fine until you try to use the misconfigured code. In this case, the misconifguration won't be used until you try to login, which triggers the IoC resolver, which accesses the config, and throws the error.

这篇关于ASP Boilerplate中LDAP实现上的InvalidCastException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆