Spring Security 4.0.0 + ActiveDirectoryLdapAuthenticationProvider + BadCredentialsException PartialResultException

时间:2015-04-15 19:14:09

标签: java spring spring-mvc authentication spring-security

我已经在stackoverflow上阅读了几乎所有关于Spring / Security / Ldap和ActiveDirectory的内容。即使我找到了有用的提示和提示,我也无法解决我的问题。

这是:我确实使用用户服务和自定义登录页面配置了Spring Security,一切正常。然后,我尝试切换到恰好是ActiveDirectory的最终身份验证提供程序。

这是我的security-applicationContext.xml(我提醒你这个设置在用户服务作为身份验证提供程序时工作正常,因此,文件实际上是导入的,等等):

<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/security"
    xmlns:oauth="http://www.springframework.org/schema/security/oauth"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://www.springframework.org/schema/security/oauth http://www.springframework.org/schema/security/spring-security-oauth.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

<!-- Définitions globales de sécurité -->
<global-method-security pre-post-annotations="enabled" />

<!-- Configuration de l'accès et du formulaire -->
<!-- Permettre l'accès libre aux feuilles de style, polices et images -->
<http pattern='/resources/css/**' security="none" />
<http pattern='/resources/fonts/**' security="none" />
<http pattern='/resources/images/**' security="none" />

<http use-expressions="true" access-denied-page="/403" disable-url-rewriting="true">

    <!-- Limitation à une seule session utilisateur concurrente -->
    <session-management invalid-session-url="/identite?time=1">
        <concurrency-control max-sessions="1" expired-url="/identite?time=1" />
    </session-management>

    <!-- Définitions pour le formulaire de la page JSP d'identification -->
    <form-login login-page="/identite" login-processing-url="/identite.proc" default-target-url="/" always-use-default-target="true" authentication-failure-url="/identite?err=1" username-parameter="username" password-parameter="password" />

    <logout logout-url="/logout" logout-success-url="/identite?out=1" delete-cookies="JSESSIONID" invalidate-session="true" />

    <!-- Utiliser un canal chiffré pour les échanges -->
    <intercept-url requires-channel="https" pattern="/identite*" access="permitAll()" />
    <intercept-url requires-channel="https" pattern="/**" access="isAuthenticated()" />
</http>

<!-- Fournisseurs d'identité pour le formulaire (au final, LDAP) -->
<authentication-manager erase-credentials="true">
    <ldap-authentication-provider ref="myADProvider" />
    <!-- This is the user-service authentication provider that is working fine
        <authentication-provider>
        <user-service>
        <user name="Toto" authorities="ROLE_USER, ROLE_ADMIN" password="totototo" />
        </user-service>
        </authentication-provider>
    -->
</authentication-manager>

<b:bean id="myADProvider"
    class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
    <b:constructor-arg value="fsappsuni" />
    <b:constructor-arg value="ldap://fsapps.company.uni:389/" />
    <b:property name="convertSubErrorCodesToExceptions" value="true" />
    <b:property name="useAuthenticationRequestCredentials" value="true" />
</b:bean>

在bean myADProvider中,即使我将第一个构造函数参数更改为fsapps.company.uni或company.uni或其他任何内容,它也不会将任何内容更改为以下错误。问题似乎是由于绑定搜索后使用错误的过滤器查找类似(&amp;(userPrincipalName = {0})(objectClass = user))而不是(&amp;(sAMAccountName = {0}) (objectClass的=用户))。由于我无法弄清楚如何使用LDAP提供程序进行更改,因此我切换到LDAP提供程序,尝试使其无法成功运行,既不会在同一个地方遇到问题。我还将我的Spring Framework从3.1.2升级到4.1.6.RELEASE和Spring Security从3.1.2升级到4.0.0.RELEASE阅读后出现了ActiveDirectoryLdapAuthenticationProvider的问题,希望在此期间问题得到解决有关此问题的原始讨论是使用版本3.x。

以下是我尝试使用Active Directory进行身份验证的LDAP设置配置:

<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/security"
    xmlns:oauth="http://www.springframework.org/schema/security/oauth"
    xsi:schemaLocation="http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-4.0.xsd
    http://www.springframework.org/schema/security/oauth
    http://www.springframework.org/schema/security/spring-security-oauth.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

<!-- Définitions globales de sécurité -->
<global-method-security pre-post-annotations="enabled" />

<!-- Configuration de l'accès et du formulaire -->
<!-- Permettre l'accès libre aux feuilles de style, polices et images -->
<http pattern='/resources/css/**' security="none" />
<http pattern='/resources/fonts/**' security="none" />
<http pattern='/resources/images/**' security="none" />

<http use-expressions="true" disable-url-rewriting="true">

    <!-- Limitation à une seule session utilisateur concurrente -->
    <session-management invalid-session-url="/identite?time=1">
        <concurrency-control max-sessions="1" expired-url="/identite?time=1" />
    </session-management>

    <!-- Définitions pour le formulaire de la page JSP d'identification -->
    <form-login login-page="/identite" login-processing-url="/identite.proc" default-target-url="/" always-use-default-target="true" authentication-failure-url="/identite?err=1" username-parameter="username" password-parameter="password" />
    <csrf disabled="true" />

    <logout logout-url="/logout" logout-success-url="/identite?out=1" delete-cookies="JSESSIONID" invalidate-session="true" />

    <!-- Utiliser un canal chiffré pour les échanges -->
    <intercept-url requires-channel="https" pattern="/identite*" access="permitAll()" />
    <intercept-url requires-channel="https" pattern="/**" access="isAuthenticated()" />
</http>

<!-- Fournisseurs d'identité pour le formulaire (au final, LDAP) -->
<ldap-server url="ldap://fsapps.company.uni/dc=fsapps,dc=company,dc=uni" port="389" />
<authentication-manager erase-credentials="true">
    <ldap-authentication-provider role-prefix="none"
        user-search-filter="(&amp;(sAMAccountName={0})(objectClass=user))"
        group-search-filter="(&amp;(member={0})(objectClass=group))"
        user-search-base="dc=fsapps,dc=company,dc=uni">
    </ldap-authentication-provider>
    <!-- <authentication-provider ref="myADProvider" ></authentication-provider> -->
</authentication-manager>

<!--
<b:bean id="myADProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
    <b:constructor-arg value="fsapps.company.uni" />
    <b:constructor-arg value="ldap://fsapps.company.uni:389/" />
    <b:property name="convertSubErrorCodesToExceptions" value="true" />
    <!- -
    <b:property name="useAuthenticationRequestCredentials" value="true" />
    - ->
</b:bean>
<b:bean id="webSecurityExpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler"></b:bean>
-->

</b:beans>

这次,我将bean配置部分留给了AD提供程序以供参考。这既不适用。

那么,如何将用户和组搜索路径传递给AD提供程序?或者,如何配置LDAP提供程序以使用AD并完成身份验证?

以下是我在日志中使用LDAP设置收到的消息,上面介绍了AD设置(错误的凭据主要是由于错误的搜索路径):

2015-04-15 16:19:13,252 DEBUG (o.s.s.a.ProviderManager.authenticate) [http-8443-1] Authentication attempt using org.springframework.security.ldap.authentication.LdapAuthenticationProvider MDC{}
2015-04-15 16:19:13,252 DEBUG (o.s.s.l.a.AbstractLdapAuthenticationProvider.authenticate) [http-8443-1] Processing authentication request for user: ba5glag MDC{}
2015-04-15 16:19:13,252 DEBUG (o.s.s.l.s.FilterBasedLdapUserSearch.searchForUser) [http-8443-1] Searching for user 'myuser', with user search [ searchFilter: '(&(sAMAccountName={0})(objectClass=user))', searchBase: 'dc=fsapps,dc=company,dc=uni', scope: subtree, searchTimeLimit: 0, derefLinkFlag: false ] MDC{}
2015-04-15 16:19:13,299 DEBUG (o.s.s.a.DefaultAuthenticationEventPublisher.publishAuthenticationFailure) [http-8443-1] No event was found for the exception org.springframework.security.authentication.InternalAuthenticationServiceException MDC{}
2015-04-15 16:19:13,299 ERROR (o.s.s.w.a.AbstractAuthenticationProcessingFilter.doFilter) [http-8443-1] An internal error occurred while trying to authenticate the user. MDC{}
org.springframework.security.authentication.InternalAuthenticationServiceException: Uncategorized exception occured during LDAP processing; nested exception is javax.naming.NamingException: [LDAP: error code 1 - 00000000: LdapErr: DSID-0C090627, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, vece(unprintable character here)]; remaining name 'dc=fsapps,dc=company,dc=uni'
        at org.springframework.security.ldap.authentication.LdapAuthenticationProvider.doAuthentication(LdapAuthenticationProvider.java:207) ~[spring-security-ldap-4.0.0.RELEASE.jar:?]
        at org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.authenticate(AbstractLdapAuthenticationProvider.java:82) ~[spring-security-ldap-4.0.0.RELEASE.jar:?]

我希望我提供所有必需的信息,以便为解决此配置问题提供一些提示和指导。

更新2015-04-16~10:20

我想出了如何使用ActiveDirectoryLdapAuthenticationProvider添加搜索过滤器。我还和Wireshark一起观看了我的应用服务器和AD服务器之间的交换,看看实际上做了什么。以下是我的发现,我相信我对问题的解决方案并不遥远。我更新了security-applicationContext.xml,因为我现在集中精力使ActiveDirectoryLdapAuthenticationProvider工作,所以我扔掉了ldap-server的东西。所以,现在这是一个更清晰的配置:

<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/security"
    xmlns:oauth="http://www.springframework.org/schema/security/oauth"
    xsi:schemaLocation="http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-4.0.xsd
    http://www.springframework.org/schema/security/oauth
    http://www.springframework.org/schema/security/spring-security-oauth.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

    <!-- Définitions globales de sécurité -->
    <global-method-security pre-post-annotations="enabled" />

    <!-- Configuration de l'accès et du formulaire -->
    <!-- Permettre l'accès libre aux feuilles de style, polices et images -->
    <http pattern='/resources/css/**' security="none" />
    <http pattern='/resources/fonts/**' security="none" />
    <http pattern='/resources/images/**' security="none" />
    <http pattern='/resources/js/**' security="none" />

    <http use-expressions="true" disable-url-rewriting="true">

        <!-- Limitation à une seule session utilisateur concurrente -->
        <session-management invalid-session-url="/identite?expiree=1">
            <concurrency-control max-sessions="1" expired-url="/identite?expiree=1" />
        </session-management>

        <!-- Définitions pour le formulaire de la page JSP d'identification -->
        <form-login login-page="/identite" login-processing-url="/identite.proc" default-target-url="/" always-use-default-target="true" authentication-failure-url="/identite?err=1" username-parameter="username" password-parameter="password" />
        <csrf disabled="true" />

        <logout logout-url="/logout" logout-success-url="/identite?termine=1" delete-cookies="JSESSIONID" invalidate-session="true" />

        <!-- Utiliser un canal chiffré pour les échanges -->
        <intercept-url requires-channel="https" pattern="/identite*" access="permitAll()" />
        <intercept-url requires-channel="https" pattern="/**" access="isAuthenticated()" />
        <access-denied-handler error-page="/erreur403" />
    </http>

    <!-- Fournisseurs d'identité pour le formulaire (au final, LDAP) -->
    <authentication-manager erase-credentials="true">
         <authentication-provider ref="myADProvider" />
    </authentication-manager>

    <b:bean id="myADProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
        <b:constructor-arg value="fsapps.company.uni" />
        <b:constructor-arg value="ldap://fsapps.company.uni:389/" />
        <b:property name="searchFilter" value="(&amp;(sAMAccountName={0})(objectClass=user))" />
        <b:property name="convertSubErrorCodesToExceptions" value="true" />
        <b:property name="useAuthenticationRequestCredentials" value="true" />
    </b:bean>
    <b:bean id="webSecurityExpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />

</b:beans>

很少有关于配置的评论。在myADProvider bean定义中,构造函数的第一个参数fsapps.company.uni用于以username@fsapps.company.uni形式创建主体,并使用以下形式的baseObject为搜索请求构建baseObject: DC = fsapps,DC =公司,DC = UNI。

第二个构造函数的参数用于建立通信。 name =&#34; searchFilter&#34;将调用ActiveDirectoryLdapAuthenticationProvider类的setSearchFilter()方法来设置搜索过滤器(这是我最初想要取得一些进展)。因此,它现在设置为搜索(&amp;(sAMAccountName = {0})(objectClass = user))。

通过此设置,我可以在WireShark中看到LDAP对话并且绑定成功。绑定响应是成功的。接下来,搜索请求也会成功,但不会返回任何结果。因此,我仍然在我的日志中获得以下内容:

2015-04-16 10:30:28,201 DEBUG (o.s.s.a.ProviderManager.authenticate) [http-8443-2] Authentication attempt using org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider MDC{}
2015-04-16 10:30:28,201 DEBUG (o.s.s.l.a.AbstractLdapAuthenticationProvider.authenticate) [http-8443-2] Processing authentication request for user: myusername MDC{}
2015-04-16 10:30:28,294 DEBUG (o.s.s.l.SpringSecurityLdapTemplate.searchForSingleEntryInternal) [http-8443-2] Searching for entry under DN '', base = 'dc=fsapps,dc=company,dc=uni', filter = '(&(sAMAccountName={0})(objectClass=user))' MDC{}
2015-04-16 10:30:28,294 INFO (o.s.s.l.SpringSecurityLdapTemplate.searchForSingleEntryInternal) [http-8443-2] Ignoring PartialResultException MDC{}
2015-04-16 10:30:28,310 DEBUG (o.s.s.w.a.AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication) [http-8443-2] Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials MDC{}
2015-04-16 10:30:28,310 DEBUG (o.s.s.w.a.AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication) [http-8443-2] Updated SecurityContextHolder to contain null Authentication MDC{}
2015-04-16 10:30:28,310 DEBUG (o.s.s.w.a.AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication) [http-8443-2] Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler@2876b359 MDC{}

我相信我几乎在那里,但如果有人可以提供任何帮助,仍然可以使用一些帮助。仔细检查后,看起来第一个构造函数的参数用于构造baseObject,而userPrincipalName是我的问题。在我们的设置中,userPrincipalName使用的域名不是LDAP网址中的域名(即campus.company.com)。到目前为止,我可以更改参数以匹配用于构建用户的域名。校长。现在,问题是这将改变必须与LDAP网址架构匹配的baseObject(即fsapps.company.uni)。

从文档中,只有一种方法可以设置搜索过滤器,但构造函数可以使用一个,两个或三个参数。第三个参数是提供baseObject值。

我的问题然后通过以下配置解决,我必须使用所有可用的setter和构造函数的参数来使其工作。 myADProvider bean定义如下:

    <b:bean id="myADProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
        <b:constructor-arg value="campus.company.com" />
        <b:constructor-arg value="ldap://fsapps.company.uni:389/" />
        <b:constructor-arg value="dc=fsapps,dc=company,dc=uni" />
        <b:property name="searchFilter" value="(&amp;(userPrincipalName={0})(objectClass=user))" />
        <b:property name="convertSubErrorCodesToExceptions" value="true" />
    </b:bean>

就我而言,我现在可以省略searchFilter属性,因为我回退了默认值。尽管对我的设置和问题进行了很长时间的描述。我希望其他人可以从中受益。

以下是ActiveDirectorLdapAuthenticationProvider类文档的链接:http://docs.spring.io/autorepo/docs/spring-security/4.0.0.RELEASE/apidocs/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.html

我发现WireShark非常有助于查看应用程序服务器和AD服务器之间发生的事情以调试此问题。

3 个答案:

答案 0 :(得分:5)

我无法使用Context.REFERRAL =&#34;关注&#34;实际上问题在于ActiveDirectoryLdapProvider类的方法searchForUser()的代码。在此方法中,使用bindPrincipal调用SpringSecurityLdapTemplate.searchForSingleEntryInternal()方法,该实际上是userPrincipalName,它是由传递给第一个参数中的构造函数的参数和用户名组成的。因此,即使您将搜索过滤器设置为userPrincipalName以外的任何其他内容,也会将userPrincipalName作为参数0传递。因此,带有sAMAccountName的过滤器将无法与UPN一起使用并抛出异常。

应修改或扩充searchForUser()以检测searchFilter是否需要用户名而不是UPN,或者提供额外的setter来使用searchFilter的模式设置参数。

但是在没有修改代码的情况下,没有办法让这个类在这种情况下正常工作。这就是我最终做的。我编写了自己的类,基本上是原始ActiveDirectoryLdapAUthenticationProvider的副本,对searchForUser()进行了一次简单的修改,将用户名而不是bindPrincipal传递给searchForSingleEntryInternal()。

你可以输入你想要的任何搜索过滤器,但强制只使用一个实际上是userPrincipalName的参数,而不是别的。

答案 1 :(得分:1)

我遇到了类似的问题并做了一些研究。在我们的AD域名&#34; my.company.com&#34;我们似乎有两组用户,一组用UPN,格式为user1@my.company.com,另一组用户名为user2@somethingelse.com。

当我使用org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider并尝试使用user1@my.company.com和user2@somethingelse.com对用户进行身份验证时 - 根据我通过的内容,只有其中一个可以运行到域名的构造函数。

我不是AD专家,但是看看微软技术网文章:https://technet.microsoft.com/en-us/library/cc739093(v=ws.10).aspx看来,虽然UPN后缀通常与域名相同,但它不是必须的,可以加上别的后缀。引用那篇文章

  

UPN的第二部分,即UPN后缀,用于标识域中的域   用户帐户所在的位置。此UPN后缀可以是DNS   域名,林中任何域的DNS名称,或者它可以是   管理员创建的备用名称,仅用于登录   目的。此备用UPN后缀不需要是有效的DNS   名。

     

在Active Directory中,默认的UPN后缀是DNS的DNS名称   用户帐户创建的域。在大多数情况下,这是   域名在Internet上注册为企业域。运用   替代域名作为UPN后缀可以提供额外的   登录安全性并简化用于登录其他域的名称   在森林里。

     

例如,如果您的组织使用深层域树,则会进行组织   根据部门和地区,域名可能会很长。默认   该域中用户的用户UPN可能是   sales.westcoast.microsoft.com。该用户的登录名   域名为user@sales.westcoast.microsoft.com。创建UPN   &#34; microsoft&#34;的后缀允许同一个用户使用   用户@ microsoft的登录名更简单。有关的更多信息   用户帐户,请参阅用户和计算机帐户以及对象名称。

但我认为ActiveDirectoryLdapAuthenticationProvider.java似乎假设域名与UPN后缀相同。我对ActiveDirectoryLdapAuthenticationProvider.java进行了本地修复,但没有做出这样的假设:

String createBindPrincipal(String username) {
    if (domain == null || username.toLowerCase().endsWith(domain) || username.contains("@")) {
            return username;
    }
    return username + "@" + domain;
}

现在,具有不同UPN后缀的两个用户都是可搜索的。如果我的假设是正确的,我可能会打开Spring安全漏洞。

答案 2 :(得分:1)

在Spring Security 4.1.1 / SpringBoot 1.4.0环境中,我这样做(在Java中):

@Configuration
public class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter
{
   public void init (AuthenticationManagerBuilder aAuth) throws Exception
   {

      ActiveDirectoryLdapAuthenticationProvider
              myProvider = new ActiveDirectoryLdapAuthenticationProvider (ldapDomain, ldapUrl);
      aAuth.authenticationProvider (myProvider);
      aAuth.eraseCredentials (false);
   }
}

我没有遇到任何问题,用户可以使用sAMAccountName登录。