我正在尝试添加辅助ldap contextSource来在拆分域环境中提高弹簧安全性,但我似乎言之凿凿。我知道之前曾问过类似的问题,但这是针对登录到同一应用程序的两个独立域的。
我的第一步是将辅助上下文源添加到我的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添加了constructor-arg,并用我的自定义类替换了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文件中确实有所有依赖项。谢谢!
最佳答案
从您的问题中尚不能完全弄清楚到底出了什么问题-例如,由于您正在使用Spring Security的BindAuthenticator
并尝试向其传递两个ContextSource
参数,因此您实际上并未加载该配置。
如果您是我,我将避免尝试破解内部实现类,而是根据您的选择标准,不理会内部实现类,并编写单独的委托类。
首先,我将定义两个单独的LdapAuthenticationProvider
bean,每个域一个,并首先确保您可以通过在单元测试中直接调用它们来对每个用户进行身份验证。尝试同时使用它们之前,请确保可以针对它们各自的域正确配置它们中的每一个。
之后,我将它们连接到单独的委派AuthenticationProvider
中。就像是:
public class DelegatingLdapAuthenticationProvider implements AuthenticationProvider {
// Inject these via the app context
private LdapAuthenticationProvider primary;
private LdapAuthenticationProvider secondary;
public Authentication authenticate(Authentication a) {
if (a.getName().contains("secondDomain")) {
return secondary.authenticate(a);
} else {
return primary.authenticate(a);
}
}
}
然后,我将这个bean配置为Spring Security实际调用的身份验证提供程序。