我正在尝试在拆分域环境中将辅助 ldap contextSource 添加到 spring 安全性,但我似乎做得不够。我意识到以前也有人问过类似的问题,但这是针对登录同一个应用程序的两个单独的域。
我的第一步是将辅助上下文源添加到我的 security-config xml 文件中,如下所示:
<beans:bean id="secondaryContextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<beans:constructor-arg value="ldap://<ldap address>:389/DC=example,DC=com"/>
<beans:property name="userDn" value="CN=BindAccount,CN=Users,DC=example,DC=com" />
<beans:property name="password" value="examplepw" />
</beans:bean>
此外,我将构造函数参数添加到 ldapAuthenticationProvider 并用我的自定义类替换了 BindAuthenticator,如下所示:
<beans:bean id="ldapAuthenticationProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<beans:constructor-arg>
<beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
<beans:constructor-arg ref="contextSource" />
<beans:constructor-arg ref="secondaryContextSource" />
<beans:property name="userSearch">
<beans:bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<beans:constructor-arg index="0" value="CN=Users"/>
<beans:constructor-arg index="1" value="(userPrincipalName={0})"/>
<beans:constructor-arg index="2" ref="contextSource" />
</beans:bean>
</beans:property>
</beans:bean>
</beans:constructor-arg>
<beans:property name="userDetailsContextMapper">
<beans:bean id="employeeServiceFacade" class="com.example.service.security.EmployeeServiceFacade" />
</beans:property>
<beans:constructor-arg>
<beans:bean class="com.example.web.security.CustomLdapAuthoritiesPopulator" />
</beans:constructor-arg>
</beans:bean>
然后我尝试扩展 BindAuthenticator 以在构造函数中接受并设置辅助上下文源。最初我无法让它工作,所以我完全重写了 BindAuthenticator 类并扩展了 AbstractLdapAuthenticator 以绕过 BindAuthenticator。然后我覆盖了 authenticate 方法以检查用户名是否包含辅助 DN,如果包含,我将再次调用 bindWithDn 以尝试重新绑定到辅助域。这就是我认为我做错了的地方,因为当它尝试获取新的 Dn 时它失败了。基本上它声明它无法绑定到域。(我已经三次检查了域设置,并使用 ldap 管理控制台连接到它,并将这些设置用于我的应用程序)这是我的扩展 BindAuthenticator
public class ExtendedBindAuthenticator extends AbstractLdapAuthenticator {
private BaseLdapPathContextSource secondaryContextSource;
public void setSecondContextSource(BaseLdapPathContextSource secondContextSource) {
this.secondaryContextSource = secondaryContextSource;
}
public ExtendedBindAuthenticator(BaseLdapPathContextSource contextSource, BaseLdapPathContextSource secondContextSource) {
super(contextSource);
this.secondaryContextSource = secondaryContextSource;
}
public DirContextOperations authenticate(Authentication authentication) {
DirContextOperations user = null;
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects");
String username = authentication.getName();
String password = (String)authentication.getCredentials();
if(username.contains("secondDomain")) {
DirContextOperations secondaryUser = getUserSearch().searchForUser(username);
this.bindWithDn(secondaryUser.getDn().toString(), username, password);
}
if (!StringUtils.hasLength(password)) {
throw new BadCredentialsException(messages.getMessage("BindAuthenticator.emptyPassword",
"Empty Password"));
}
// If DN patterns are configured, try authenticating with them directly
for (String dn : getUserDns(username)) {
user = this.bindWithDn(dn, username, password);
if (user != null) {
break;
}
}
// Otherwise use the configured search object to find the user and authenticate with the returned DN.
if (user == null && getUserSearch() != null) {
DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
user = bindWithDn(userFromSearch.getDn().toString(), username, password);
}
if (user == null) {
throw new BadCredentialsException(
messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
}
return user;
}
private DirContextOperations bindWithDn(String userDnStr, String username, String password) {
BaseLdapPathContextSource ctxSource = (BaseLdapPathContextSource) getContextSource();
if(username.contains("secondDomain")) {
ctxSource = secondaryContextSource;
}
DistinguishedName userDn = new DistinguishedName(userDnStr);
DistinguishedName fullDn = new DistinguishedName(userDn);
fullDn.prepend(ctxSource.getBaseLdapPath());
DirContext ctx = null;
try {
ctx = getContextSource().getContext(fullDn.toString(), password);
// Check for password policy control
PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor.extractControl(ctx);
Attributes attrs = ctx.getAttributes(userDn, getUserAttributes());
DirContextAdapter result = new DirContextAdapter(attrs, userDn, ctxSource.getBaseLdapPath());
if (ppolicy != null) {
result.setAttributeValue(ppolicy.getID(), ppolicy);
}
return result;
} catch (NamingException e) {
// This will be thrown if an invalid user name is used and the method may
// be called multiple times to try different names, so we trap the exception
// unless a subclass wishes to implement more specialized behaviour.
if ((e instanceof org.springframework.ldap.AuthenticationException)
|| (e instanceof org.springframework.ldap.OperationNotSupportedException)) {
handleBindException(userDnStr, username, e);
} else {
throw e;
}
} catch (javax.naming.NamingException e) {
throw LdapUtils.convertLdapException(e);
} finally {
LdapUtils.closeContext(ctx);
}
return null;
}
/**
* Allows subclasses to inspect the exception thrown by an attempt to bind with a particular DN.
* The default implementation just reports the failure to the debug logger.
*/
protected void handleBindException(String userDn, String username, Throwable cause) {
System.out.println("Failed to bind as " + userDn + ": " + cause);
}
}
如果有人对这种事情有任何经验,我将不胜感激,因为我在这个主题上找不到太多。我希望有人能告诉我我是否走在正确的轨道上,或者我是否应该以不同的方式解决这个问题。为了清楚起见,我使用的是 spring-security-ldap 而不是 spring-ldap。还只想提一下,我的 pom 文件中确实有我的所有依赖项。谢谢!