我还在学习LDAP / Active Directory,所以如果我的术语完全错误,请纠正我:)
在我们的Java Web应用程序中,我正在尝试使用Spring Security LDAP来保护它。我设法让Spring Security使用内存中的身份验证,但我们需要将它绑定到我们的AD服务器上。
我将使用 com.test
屏蔽我们的实际域名以下是我尝试从我的应用程序登录时收到的错误
13:39:55,701 ERROR ActiveDirectoryLdapAuthenticationProvider:133 - 无法找到经过身份验证的用户的目录条目:johnsmit javax.naming.NameNotFoundException:[LDAP:错误代码32 - 0000208D: NameErr:DSID-0310020A,问题2001(NO_OBJECT),数据0,最佳匹配 of:'CN = Users,DC = ad,DC = test,DC = com'
我在Spring中使用基于类的配置 这是我的SecurityConfiguration类
@Configuration
@EnableWebMvcSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
provider = new ActiveDirectoryLdapAuthenticationProvider("ad.test.com", "ldap://servername.ad.test.com:389/cn=Users,dc=ad,dc=test,dc=com");
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().failureUrl("/login?error")
.loginPage("/login")
.permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login")
.permitAll()
.and()
.httpBasic();
}
}
所以这是问题(至少我认为)...... 在我们的AD服务器中,我们有 cn ,名称, sAMAccountName 和 uid 作为我们登录的用户名, johnsmit 在上面的例子中。
我们的userPrincipalName(在我们的AD服务器中)是我们的电子邮件地址,所以 john.smith@test.com 。
我正在查看ActiveDirectoryLdapAuthenticationProvider类,它说它使用userPrincipalName。查看代码here on github,它表明它正在使用userPrincipalName。我检查了Spring Security的新版本,它不是一般可用性,但它是一样的。
如果是searchFilter
String searchFilter = "(&(objectClass=user)(sAMAccountName={0}))";
这将是理想的情况,但我不知道是否可以覆盖任何地方,我找不到任何文件?
答案 0 :(得分:1)
答案 1 :(得分:1)
当我发现这对解决自己的问题非常有帮助时,我想补充一下,@JanTheGun建议的更改似乎已合并到Spring Security 5.0.8.RELEASE / Spring Boot 2.0中。 5
ActiveDirectoryLdapAuthenticationProvider.setSearchFilter(String)
的JavaDoc读取:
用于搜索正在认证的用户的LDAP过滤器字符串。
{0}
的出现被username@domain
取代。{1}
的出现仅被替换为用户名。默认为:
(&(objectClass=user)(userPrincipalName={0})))
因此,在Spring Security 5.0.8.RELEASE中,可以使用建议的搜索过滤器更改,而无需复制任何Spring Security类!
答案 2 :(得分:0)
三年后,我也在努力解决这个问题。由于建议将userPrincipalName更改为Office365的邮件地址(请参阅Office 365 – Why Your UPN Should Match Your Primary SMTP Address),我认为其他人可能也有问题 - 所以这是我的修复。
关于github的讨论正在讨论这个问题: https://github.com/spring-projects/spring-security/issues/2448
用户gkibilov的答案解决了我的问题。我们的想法是改变" searchForUser" ActiveDirectoryLdapAuthenticationProvider类中的方法,不仅要传递bindprincipal(= USERNAME @ DOMAIN),还要传递用户名。
之后,可以使用以下searchFilter来查找sAMAccountName而不是userPrincipalName:
"(&(objectClass=user)(sAMAccountName={1}))"
以下是我执行的单个步骤:
复制 ActiveDirectoryAuthenticationException 类的源代码 到一个新类并重命名它(即" MyActiveDirectoryAuthenticationException "):SourceLink
复制 ActiveDirectoryLdapAuthenticationProvider 的来源 类到一个新类并重命名它(即 " MyActiveDirectoryLdapAuthenticationProvider "):SourceLink
使用新的异常类( MyActiveDirectoryAuthenticationException )交换 MyActiveDirectoryLdapAuthenticationProvider 中的 ActiveDirectoryAuthenticationException 类
更改 MyActiveDirectoryAuthenticationException 类中的方法以传递用户名:
private DirContextOperations searchForUser(DirContext context, String username)
throws NamingException {
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String bindPrincipal = createBindPrincipal(username);
String searchRoot = rootDn != null ? rootDn
: searchRootFromPrincipal(bindPrincipal);
try {
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context,
searchControls, searchRoot, searchFilter,
new Object[] { bindPrincipal, username});
}
...
}
更改searchFilter以查找sAMAccountName属性。在你的情况下,bean应该是这样的:
@Bean
public MyActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
provider = new MyActiveDirectoryLdapAuthenticationProvider("ad.test.com", "ldap://servername.ad.test.com:389/cn=Users,dc=ad,dc=test,dc=com");
provider.setSearchFilter("(&(objectClass=user)(sAMAccountName={1}))");
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
我不得不承认,复制课程并不是最好的解决方案,但原来的课程是#34; final"我无法找到解决此问题的更好方法。