问题
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:
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");
}
}
}
The application loads but the an error here prevents logging in
And this is what is showing in the logs
Any help would be greatly appreciated thanks.
回答1:
tl;dr;
Your problem is in CoreModule.cs
Configuration.Modules.ZeroLdap().Enable(typeof(LdapSettings));
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.
How you could have figured this out
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!
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!
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
.
All the config happens in the CoreModule.cs
file, so let's look there. You have a call to
IocManager.Register<ILdapSettings, 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));
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.
- Because the parameter is a
Type
, there won't be compile time checking if we've passed an appropriate type. 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.
Why the code "seems to run fine"
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.
来源:https://stackoverflow.com/questions/62189660/invalidcastexception-on-an-ldap-implementation-in-asp-boilerplate