Spring 安全 4.0.0 + ActiveDirectoryLdapAuthenticationProvider + BadCredentialsException PartialResultException

Spring Security 4.0.0 + ActiveDirectoryLdapAuthenticationProvider + BadCredentialsException PartialResultException

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

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

这是我的安全-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 或其他任何内容,它也不会对以下错误进行任何更改。问题似乎是由于绑定后使用错误的过滤器执行搜索以查找类似 (&(userPrincipalName={0})(objectClass=user)) 而不是 (&(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.

版本中进行的

这是我的 LDAP 设置配置,试图使身份验证与 Active Directory 一起工作:

<?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 服务器之间的交换,以了解实际发生了什么。这是我的发现,我相信我离解决问题不远了。我更新的安全性-applicationContext.xml,我已经扔掉了 ldap-server 的东西,因为我现在专注于让 ActiveDirectoryLdapAuthenticationProvider 工作。所以,现在这是一个更清晰的配置:

<?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="searchFilter" 的 属性 类型的下一个元素将调用 class ActiveDirectoryLdapAuthenticationProvider 的 setSearchFilter() 方法来设置搜索过滤器(这是我最初寻求取得一些进展的方法).因此,它现在设置为搜索 (&(sAMAccountName={0})(objectClass=user)).

通过此设置,我可以在 WireShark 中看到 LDAP 对话并且绑定成功。绑定响应成功。接下来,搜索请求也成功了,但没有 return 任何结果。因此,我的日志中仍然出现以下内容:

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 url(即 campus.company.com)中的域名。到目前为止一切顺利,然后我可以更改参数以匹配我们用于构建用户主体的域名。现在,问题是这将更改必须匹配 LDAP url 架构(即 fsapps.company.uni)的 baseObject。

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

然后我的问题通过以下配置解决,我必须使用所有可用的设置器和构造函数的参数才能使其正常工作。 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 class 文档的 link:http://docs.spring.io/autorepo/docs/spring-security/4.0.0.RELEASE/apidocs/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.html

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

我对类似问题进行了 运行 排序并进行了一些研究。在域为 "my.company.com" 的 AD 中,我们似乎有两组用户,一组的 UPN 格式为 user1@my.company.com,另一组的格式为 user2@somethingelse.com.

当我使用 org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider 并尝试使用 user1@my.company.com 和 user2@somethingelse.com 对用户进行身份验证时 - 只有其中一个有效,具体取决于我传递给构造函数的内容域名。

我不是 AD 专家,但查看 Microsoft tech net 文章:https://technet.microsoft.com/en-us/library/cc739093(v=ws.10).aspx UPN 后缀通常与域名相同,但它不是必须的,可以添加其他后缀。引用那篇文章

The second part of the UPN, the UPN suffix, identifies the domain in which the user account is located. This UPN suffix can be the DNS domain name, the DNS name of any domain in the forest, or it can be an alternative name created by an administrator and used just for log on purposes. This alternative UPN suffix does not need to be a valid DNS name.

In Active Directory, the default UPN suffix is the DNS name of the domain in which user account created. In most cases, this is the domain name registered as the enterprise domain on the Internet. Using alternative domain names as the UPN suffix can provide additional logon security and simplify the names used to log on to another domain in the forest.

For example, if your organization uses a deep domain tree, organized by department and region, domain names can get quite long. The default user UPN for a user in that domain might be sales.westcoast.microsoft.com. The logon name for a user in that domain would be user@sales.westcoast.microsoft.com. Creating a UPN suffix of "microsoft" would allow that same user to log on using the much simpler logon name of user@microsoft. For more information about user accounts, see User and computer accounts and Object names.

但我认为 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 安全性的错误。

我无法使用 Context.REFERRAL = "follow" 解决这个问题,实际上问题出在 class ActiveDirectoryLdapProvider 的方法 searchForUser() 的代码中。在此方法中,使用 bindPrincipal 调用方法 SpringSecurityLdapTemplate.searchForSingleEntryInternal(),bindPrincipal 实际上是由第一个参数中传递给构造函数的参数和用户名组成的 userPrincipalName。因此,即使您将搜索过滤器设置为除 userPrincipalName 之外的任何其他内容,它也会作为参数 0 传递 userPrincipalName。因此,具有 sAMAccountName 的过滤器将无法与 UPN 一起使用并引发异常。

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

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

有点废话,你可以输入任何你想要的搜索过滤器,但被迫只使用一个参数,实际上是 userPrincipalName,没有别的。

在 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.

登录