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 docs,Enable
方法将 auth 来源类型作为参数,但您已经传递了设置类型。改为使用 LdapAuthenticationSource
。
你是怎么想出来的
错误消息说从 LdapSettings
到 IExternalAuthenticationSource
的转换失败。这很奇怪,因为您的代码没有理由尝试在这些类型之间进行转换!
如果向下看堆栈,您会发现错误发生在 TokenAuthController
的 Authenticate
/ GetLoginResultAsync
方法中。您可以检查该方法中的代码,您可能找不到任何直接提及 LdapSettings
或 IExternalAuthenticationSource
的地方。但是,您会发现对 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
绝对是正确参数的合理可能性。但是,有两个充分的理由进一步研究这种方法。
- 因为参数是
Type
,所以编译时不会检查我们是否传递了合适的类型。
LdapSettings
是实际异常的一部分,因此任何使用它的方法都是可疑的
这将我们带到 the documentation 我们看到问题的地方。我们需要传递授权源,而不是设置。
为什么代码“看起来 运行 没问题”
配置使用了 Type
参数而不是泛型。这意味着没有编译时检查您是否传递了有效类型(如上所述)。程序编译 运行 正常 直到您尝试使用配置错误的代码 。在这种情况下,在您尝试登录之前不会使用错误配置,这会触发 IoC 解析器,它访问配置并抛出错误。
大家好,最近不得不为我们的应用程序实施 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 docs,Enable
方法将 auth 来源类型作为参数,但您已经传递了设置类型。改为使用 LdapAuthenticationSource
。
你是怎么想出来的
错误消息说从 LdapSettings
到 IExternalAuthenticationSource
的转换失败。这很奇怪,因为您的代码没有理由尝试在这些类型之间进行转换!
如果向下看堆栈,您会发现错误发生在 TokenAuthController
的 Authenticate
/ GetLoginResultAsync
方法中。您可以检查该方法中的代码,您可能找不到任何直接提及 LdapSettings
或 IExternalAuthenticationSource
的地方。但是,您会发现对 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
绝对是正确参数的合理可能性。但是,有两个充分的理由进一步研究这种方法。
- 因为参数是
Type
,所以编译时不会检查我们是否传递了合适的类型。 LdapSettings
是实际异常的一部分,因此任何使用它的方法都是可疑的
这将我们带到 the documentation 我们看到问题的地方。我们需要传递授权源,而不是设置。
为什么代码“看起来 运行 没问题”
配置使用了 Type
参数而不是泛型。这意味着没有编译时检查您是否传递了有效类型(如上所述)。程序编译 运行 正常 直到您尝试使用配置错误的代码 。在这种情况下,在您尝试登录之前不会使用错误配置,这会触发 IoC 解析器,它访问配置并抛出错误。