简体   繁体   English

Spring MVC应用程序向Active Directory进行身份验证

[英]Spring MVC application authenticating to Active Directory

I realize that this question has been asked already, but I haven't found a solution in any other post that matches my situation. 我已经意识到这个问题已经被提出,但我没有找到符合我情况的任何其他帖子的解决方案。

I am writing a Spring MVC java application, and I am trying to get it to authenticate against our Active Directory system. 我正在编写一个Spring MVC java应用程序,我试图让它对我们的Active Directory系统进行身份验证。 I am using Spring Tools Suite 3.4.0 and have created a Spring MVC project. 我正在使用Spring Tools Suite 3.4.0并创建了一个Spring MVC项目。 I am using Spring Security 3.1.1, and once completed, this application will be deployed to a Tomcat 7 java server running on Linux. 我正在使用Spring Security 3.1.1,一旦完成,该应用程序将被部署到在Linux上运行的Tomcat 7 java服务器上。

I have a user in our Active Directory system whose password I know is valid since I have other applications that authenticate against this same Active Directory system, and I can successfully authenticate with this user in the other applications. 我的Active Directory系统中有一个用户,我知道密码是有效的,因为我有其他应用程序对同一个Active Directory系统进行身份验证,我可以在其他应用程序中成功地与该用户进行身份验证。 The userid is: 用户标识是:

myuser@mycompany.com myuser@mycompany.com

You can see that the userids stored in our Active Directory system are in the format of email address. 您可以看到存储在Active Directory系统中的用户标识采用电子邮件地址的格式。

The Domain Controller in our Active Directory system is: addomain.mycompany.com. 我们的Active Directory系统中的域控制器是:addomain.mycompany.com。 Underneath the Domain Controller is an OU called ExternalUsers, and this OU has two sub OUs called Groups and Users. 域控制器下面是一个名为ExternalUsers的OU,这个OU有两个子OU,分别叫做Groups和Users。 So the path to my user would be: 所以我的用户的路径是:

CN=myuser@mycompany.com,OU=Users,OU=ExternalUsers,DC=addomain,DC=mycompany,DC=com CN =为myuser @ mycompany.com,OU =用户,OU = ExternalUsers,DC = addomain,DC = myCompany中,DC = com的

The intercept works just fine...whenever I try navigating to any url in the application, I get redirected to the login page. 拦截工作得很好......每当我尝试导航到应用程序中的任何URL时,我都会被重定向到登录页面。 The trouble that I'm having is with authentication. 我遇到的麻烦是验证。 When I put in my userid and password and click Submit, the authentication fails. 当我输入我的用户ID和密码并单击“提交”时,身份验证失败。 This is what I'm seeing in the logs: 这就是我在日志中看到的:

2014-05-12 08:42:32,916 DEBUG: org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider - Processing authentication request for user: myuser@mycompany.com
2014-05-12 08:42:33,383 DEBUG: org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider - Authentication for myuser@mycompany.com@addomain failed:
javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1]
2014-05-12 08:42:33,384 INFO : org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider - Active Directory authentication failed: Supplied password was invalid

I have listed the relevant files in my project below. 我在下面的项目中列出了相关文件。

In the spring-security-context.xml file, I have tried changing this line: 在spring-security-context.xml文件中,我尝试更改此行:

<beans:constructor-arg value="addomain" />

to this: 对此:

<beans:constructor-arg value="addomain.mycompany.com" />

Unfortunately, I am seeing the same behavior. 不幸的是,我看到了同样的行为。 The logs are the same as above. 日志与上面相同。

I have also tried switching the following line in spring-security-context.xml from this: 我也试过在spring-security-context.xml中切换以下行:

<security:intercept-url pattern="/**" access="ROLE_ADMIN" />

to this: 对此:

<security:intercept-url pattern="/**" access="ROLE_USER" />

But unfortunately I get the same results. 但不幸的是,我得到了相同的结果。 Do I need to create an Active Directory group whose name matches the role name (ie, ROLE_ADMIN or ROLE_USER)? 我是否需要创建名称与角色名称匹配的Active Directory组(即ROLE_ADMIN或ROLE_USER)?

I found another post that seems to match the issue that I'm having: 我发现另一篇文章似乎与我遇到的问题相符:

Spring ActiveDirectoryLdapAuthenticationProvider handleBindException - Supplied password was invalid error Spring ActiveDirectoryLdapAuthenticationProvider handleBindException - 提供的密码无效错误

Unfortunately it didn't really provide a solution. 不幸的是,它并没有真正提供解决方案。

Something that I discovered is that the Active Directory users that I have been working with had sAMAccountName values that did not match the userid. 我发现的一点是,我一直在使用的Active Directory用户的sAMAccountName值与userid不匹配。 I created a new user whose sAMAccountName matched the userid, and suddenly I got past the "Supplied password was invalid" message. 我创建了一个新用户,其sAMAccountName与用户ID匹配,突然我超过了“提供的密码无效”消息。 However, now I'm getting the following message when trying to log in: 但是,现在我在尝试登录时收到以下消息:

org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 0 org.springframework.dao.IncorrectResultSizeDataAccessException:结果大小不正确:预期1,实际0

The full stack trace is: 完整的堆栈跟踪是:

2014-05-12 13:34:53,488 DEBUG: org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider - Processing authentication request for user: myuser@mycompany.com
2014-05-12 13:34:53,578 INFO : org.springframework.security.ldap.SpringSecurityLdapTemplate - Ignoring PartialResultException
May 12, 2014 1:34:53 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [appServlet] in context with path [/pima] threw exception
org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 0
    at org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForSingleEntryInternal(SpringSecurityLdapTemplate.java:239)
    at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.searchForUser(ActiveDirectoryLdapAuthenticationProvider.java:258)
    at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.doAuthentication(ActiveDirectoryLdapAuthenticationProvider.java:114)
    at org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.authenticate(AbstractLdapAuthenticationProvider.java:61)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
    at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:94)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:194)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:155)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:722)

I'm kind of stuck at this point can anyone help me understand what it is that I'm doing wrong here and/or what do I need to do to get this to work? 我有点被困在这一点上,任何人都可以帮助我理解我在这里做错了什么和/或我需要做些什么才能让它发挥作用?

Here's my login.jsp file: 这是我的login.jsp文件:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<form method="post" id="loginForm" action="<c:url value='/j_spring_security_check' >    </c:url>" >
     <table>
<tr>
            <td>
            UserId:
        </td>
        <td>
            <input type="text" size="50" name="j_username"></input>
        </td>
    </tr>
    <tr>
        <td>
            Password:
        </td>
        <td>
            <input type="password" name="j_password"></input>
        </td>
    </tr>
</table>
<br />

<input type="submit"></input>
</form>
</body>
</html>

Here's my web.xml file: 这是我的web.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>

<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Processes application requests -->
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

Here's my servlet-context.xml file: 这是我的servlet-context.xml文件:

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

<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />

<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".jsp" />
</beans:bean>

<context:component-scan base-package="com.mycompany.pima" />

<!-- Create DataSource Bean for connection to the SQL Server database -->
<beans:bean id="dbDataSource"
    class="org.springframework.jndi.JndiObjectFactoryBean">
    <beans:property name="jndiName" value="java:comp/env/jdbc/MyDB"/>
</beans:bean>

<beans:bean id="adSettings"
    class="org.springframework.jndi.JndiObjectFactoryBean">
    <beans:property name="jndiName" value="java:comp/env/adSettings"/>
</beans:bean>

<beans:bean id="portalSettings"
    class="org.springframework.jndi.JndiObjectFactoryBean">
    <beans:property name="jndiName" value="java:comp/env/portalSettings"/>
</beans:bean>

</beans:beans>

Here's my root-context.xml file: 这是我的root-context.xml文件:

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

<!-- Root Context: defines shared resources visible to all other web components -->

<import resource="spring-security-context.xml"/>

</beans>

Here's my spring-security-context.xml file: 这是我的spring-security-context.xml文件:

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

<security:http pattern="/login" security="none" />
<security:http pattern="/logerror" security="none" />

<!-- LDAP server details -->
<security:authentication-manager>
    <security:authentication-provider ref="ldapActiveDirectoryAuthProvider" />
</security:authentication-manager>

<beans:bean id="grantedAuthoritiesMapper" class="com.mycompany.pima.security.ActiveDirectoryGrantedAuthoritiesMapper"/>

<beans:bean id="ldapActiveDirectoryAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
    <beans:constructor-arg value="addomain" />
    <beans:constructor-arg value="ldap://dev_ad_system.addomain.mycompany.com:389/" />
    <beans:property name="authoritiesMapper" ref="grantedAuthoritiesMapper" />
    <beans:property name="useAuthenticationRequestCredentials" value="true" />
    <beans:property name="convertSubErrorCodesToExceptions" value="true" />
</beans:bean>

<security:http auto-config="true" pattern="/**">
    <!-- Login pages -->
    <security:form-login login-page="/login" default-target-url="/users"
        login-processing-url="/j_spring_security_check" authentication-failure-url="/login?error=true" />
    <security:logout logout-success-url="/login"/>

       <!-- Security zones -->
    <security:intercept-url pattern="/**" access="ROLE_ADMIN" />

</security:http>

</beans:beans>

And lastly, here's my pom.xml file: 最后,这是我的pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>pima</artifactId>
<name>PIMA</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
    <java-version>1.6</java-version>
    <org.springframework-version>3.1.1.RELEASE</org.springframework-version>
    <spring.security.version>3.1.1.RELEASE</spring.security.version>
    <org.aspectj-version>1.6.10</org.aspectj-version>
    <org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
<dependencies>
    <!-- Spring -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${org.springframework-version}</version>
        <exclusions>
            <!-- Exclude Commons Logging in favor of SLF4j -->
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
             </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>

    <!-- AspectJ -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${org.aspectj-version}</version>
    </dependency>   

    <!-- Logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${org.slf4j-version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.15</version>
        <exclusions>
            <exclusion>
                <groupId>javax.mail</groupId>
                <artifactId>mail</artifactId>
            </exclusion>
            <exclusion>
                <groupId>javax.jms</groupId>
                <artifactId>jms</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jdmk</groupId>
                <artifactId>jmxtools</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jmx</groupId>
                <artifactId>jmxri</artifactId>
            </exclusion>
        </exclusions>
        <scope>runtime</scope>
    </dependency>

    <!-- @Inject -->
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>

    <!-- Servlet -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

    <!-- Test -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.7</version>
        <scope>test</scope>
    </dependency>        

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>${spring.security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>${spring.security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>${spring.security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>${spring.security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-ldap</artifactId>
        <version>${spring.security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.ldap</groupId>
        <artifactId>spring-ldap-core</artifactId>
        <version>1.3.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>net.sourceforge.jtds</groupId>
        <artifactId>jtds</artifactId>
        <version>1.3.1</version>
    </dependency>
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-eclipse-plugin</artifactId>
            <version>2.9</version>
            <configuration>
                <additionalProjectnatures>
                    <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                </additionalProjectnatures>
                <additionalBuildcommands>
                    <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                </additionalBuildcommands>
                <downloadSources>true</downloadSources>
                <downloadJavadocs>true</downloadJavadocs>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.5.1</version>
            <configuration>
                <source>1.6</source>
                <target>1.6</target>
                <compilerArgument>-Xlint:all</compilerArgument>
                <showWarnings>true</showWarnings>
                <showDeprecation>true</showDeprecation>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.2.1</version>
            <configuration>
                <mainClass>org.test.int1.Main</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>
</project>

Thank you, 谢谢,

-Stephen Spalding - 斯蒂芬斯伯丁

Update...I believe that I have solved my problem. 更新......我相信我已经解决了我的问题。

First, the original issue that I was dealing with was where I was receiving the message below when trying to log in with a known good userid/password: 首先,我正在处理的原始问题是我在尝试使用已知良好的用户ID /密码登录时收到以下消息:

org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider - Active Directory authentication failed: Supplied password was invalid

I can confirm that this issue was resolved once I created a new user whose userid and sAMAccountName values in Active Directory were the same. 我可以确认,一旦我创建了一个新用户,其Active Directory中的userid和sAMAccountName值相同,就解决了这个问题。 The authentication piece did not work until I did this and started testing with that user. 在我执行此操作并开始使用该用户进行测试之前,身份验证部分无效。

The second issue that I dealt with was after I started using the user with matching userid and sAMAccountNames. 我处理的第二个问题是在我开始使用具有匹配的userid和sAMAccountNames的用户之后。 I started receiving the following message when trying to log in: 我在尝试登录时开始收到以下消息:

org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 0

I ended up implementing my own custom authentication provider and had to make some tweaks to it to get it to work with our Active Directory system. 我最终实现了自己的自定义身份验证提供程序,并不得不对它进行一些调整,以使其与我们的Active Directory系统一起使用。 I have posted each of the class files that I created below. 我已经发布了我在下面创建的每个类文件。

Lastly, there must exist an Active Directory group that matches the constant's value in the ActiveDirectoryGrantedAuthoritiesMapper class, and your user must be included in this group. 最后,必须存在与ActiveDirectoryGrantedAuthoritiesMapper类中的常量值匹配的Active Directory组,并且您的用户必须包含在此组中。 See this line in that file: 在该文件中查看此行:

private String ROLE_ADMIN = "ExtranetUsers";

There's probably more issues to solve that I haven't encountered yet, but I can say that I am now successfully logging to my Spring application via Active Directory. 我还没有遇到过更多要解决的问题,但我可以说我现在通过Active Directory成功登录到我的Spring应用程序。

I put all of the class files that I created for my custom authentication provider in a package called com.mycompany.pima.security. 我将我为自定义身份验证提供程序创建的所有类文件放在名为com.mycompany.pima.security的包中。 Please note that implementing this custom authentication provider is what I did in addition to the configurations in login.jsp, web.xml, servlet-context.xml, root-context.xml, spring-security-context.xml and pom.xml. 请注意,除了login.jsp,web.xml,servlet-context.xml,root-context.xml,spring-security-context.xml和pom.xml中的配置之外,我还要实现此自定义身份验证提供程序。 The files in the the com.mycompany.pima.security package are: com.mycompany.pima.security包中的文件是:

ActiveDirectoryLdapAuthenticationProvider.java: (see my comments in the searchForUser() method for what I did that finally worked) ActiveDirectoryLdapAuthenticationProvider.java :(请参阅我在searchForUser()方法中的注释,了解我最终的工作)

package com.mycompany.pima.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import     org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.OperationNotSupportedException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.ldap.InitialLdapContext;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
private static final Pattern SUB_ERROR_CODE = Pattern.compile(".*data\\s([0-9a-f]{3,4}).*");

// Error codes
private static final int USERNAME_NOT_FOUND = 0x525;
private static final int INVALID_PASSWORD = 0x52e;
private static final int NOT_PERMITTED = 0x530;
private static final int PASSWORD_EXPIRED = 0x532;
private static final int ACCOUNT_DISABLED = 0x533;
private static final int ACCOUNT_EXPIRED = 0x701;
private static final int PASSWORD_NEEDS_RESET = 0x773;
private static final int ACCOUNT_LOCKED = 0x775;

private final String domain;
private final String rootDn;
private final String url;
private boolean convertSubErrorCodesToExceptions;

private static final Logger logger = LoggerFactory.getLogger(ActiveDirectoryLdapAuthenticationProvider.class);

// Only used to allow tests to substitute a mock LdapContext
ContextFactory contextFactory = new ContextFactory();

/**
 * @param domain the domain for which authentication should take place
 */
//    public ActiveDirectoryLdapAuthenticationProvider(String domain) {
//        this (domain, null);
//    }

/**
 * @param domain the domain name (may be null or empty)
 * @param url an LDAP url (or multiple URLs)
 */
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) {

    Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
    this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
    //this.url = StringUtils.hasText(url) ? url : null;
    this.url = url;
    rootDn = this.domain == null ? null : rootDnFromDomain(this.domain);
}

@Override
protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth) {

    String username = auth.getName();
    String password = (String)auth.getCredentials();
    DirContext ctx = bindAsUser(username, password);

    try {
        return searchForUser(ctx, username);

    } catch (NamingException e) {
        logger.error("Failed to locate directory entry for authenticated user: " + username, e);
        throw badCredentials(e);
    } finally {
        LdapUtils.closeContext(ctx);
    }
}

/**
 * Creates the user authority list from the values of the {@code memberOf} attribute obtained from the user's
 * Active Directory entry.
 */
@Override
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) {
    String[] groups = userData.getStringAttributes("memberOf");

    if (groups == null) {
        logger.debug("No values for 'memberOf' attribute.");
        return AuthorityUtils.NO_AUTHORITIES;
    }

    if (logger.isDebugEnabled()) {
        logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
    }

    ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(groups.length);

    for (String group : groups) {
        authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
    }

    return authorities;
}

private DirContext bindAsUser(String username, String password) {
    // TODO. add DNS lookup based on domain
    final String bindUrl = url;

    Hashtable<String,String> env = new Hashtable<String,String>();
    env.put(Context.SECURITY_AUTHENTICATION, "simple");

    String bindPrincipal = createBindPrincipal(username);

    env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
    env.put(Context.PROVIDER_URL, bindUrl);
    env.put(Context.SECURITY_CREDENTIALS, password);
    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName());

    try {
        return contextFactory.createContext(env);
    } catch (NamingException e) {
        if ((e instanceof AuthenticationException) || (e instanceof OperationNotSupportedException)) {
            handleBindException(bindPrincipal, e);
            throw badCredentials(e);
        } else {
            throw LdapUtils.convertLdapException(e);
        }
    }
}

void handleBindException(String bindPrincipal, NamingException exception) {

    if (logger.isDebugEnabled()) {
        logger.debug("Authentication for " + bindPrincipal + " failed:" + exception);
    }

    int subErrorCode = parseSubErrorCode(exception.getMessage());

    if (subErrorCode > 0) {
        logger.info("Active Directory authentication failed: " + subCodeToLogMessage(subErrorCode));

        if (convertSubErrorCodesToExceptions) {
            raiseExceptionForErrorCode(subErrorCode, exception);
        }
    } else {
        logger.debug("Failed to locate AD-specific sub-error code in message");
    }
}

int parseSubErrorCode(String message) {
    Matcher m = SUB_ERROR_CODE.matcher(message);

    if (m.matches()) {
        return Integer.parseInt(m.group(1), 16);
    }

    return -1;
}

void raiseExceptionForErrorCode(int code, NamingException exception) {

    String hexString = Integer.toHexString(code);
    Throwable cause = new ActiveDirectoryAuthenticationException(hexString, exception.getMessage(), exception);
    switch (code) {
        case PASSWORD_EXPIRED:
            throw new CredentialsExpiredException(messages.getMessage("LdapAuthenticationProvider.credentialsExpired",
                    "User credentials have expired"), cause);
        case ACCOUNT_DISABLED:
            throw new DisabledException(messages.getMessage("LdapAuthenticationProvider.disabled",
                    "User is disabled"), cause);
        case ACCOUNT_EXPIRED:
            throw new AccountExpiredException(messages.getMessage("LdapAuthenticationProvider.expired",
                    "User account has expired"), cause);
        case ACCOUNT_LOCKED:
            throw new LockedException(messages.getMessage("LdapAuthenticationProvider.locked",
                    "User account is locked"), cause);
        default:
            throw badCredentials(cause);
    }
}

String subCodeToLogMessage(int code) {
    switch (code) {
        case USERNAME_NOT_FOUND:
            return "User was not found in directory";
        case INVALID_PASSWORD:
            return "Supplied password was invalid";
        case NOT_PERMITTED:
            return "User not permitted to logon at this time";
        case PASSWORD_EXPIRED:
            return "Password has expired";
        case ACCOUNT_DISABLED:
            return "Account is disabled";
        case ACCOUNT_EXPIRED:
            return "Account expired";
        case PASSWORD_NEEDS_RESET:
            return "User must reset password";
        case ACCOUNT_LOCKED:
            return "Account locked";
    }

    return "Unknown (error code " + Integer.toHexString(code) +")";
}

private BadCredentialsException badCredentials() {
    return new BadCredentialsException(messages.getMessage(
                    "LdapAuthenticationProvider.badCredentials", "Bad credentials"));
}

private BadCredentialsException badCredentials(Throwable cause) {
    return (BadCredentialsException) badCredentials().initCause(cause);
}

@SuppressWarnings("deprecation")
private DirContextOperations searchForUser(DirContext ctx, String username) throws NamingException {
    SearchControls searchCtls = new SearchControls();
    searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);

    // This was the original setting for searchFilter:
    // String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))";

    // These are some of the values that I played around with:
    // String searchFilter = "(&(objectClass=user)(|(userPrincipalName={0})(sAMAccountName={1})))";
    // String searchFilter = "(&(objectClass=user)((sAMAccountName={1})))";
    // String searchFilter = "(&(sAMAccountName=" + username + "))";

    // This is the final searchFilter value that I used that actually worked:
    String searchFilter = "(&(cn=" + username + "))";

    final String bindPrincipal = createBindPrincipal(username);

    String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal);
    // This is also something that I had to add to match my OU path:
    searchRoot = "ou=ExternalUsers," + searchRoot;

    try {
        return SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx, searchCtls, searchRoot, searchFilter,
            new Object[]{bindPrincipal});
    } catch (IncorrectResultSizeDataAccessException incorrectResults) {
        if (incorrectResults.getActualSize() == 0) {
            UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException("User " + username + " not found in directory.", username);
            userNameNotFoundException.initCause(incorrectResults);
            throw badCredentials(userNameNotFoundException);
        }
        // Search should never return multiple results if properly configured, so just rethrow
        throw incorrectResults;
    }
}

private String searchRootFromPrincipal(String bindPrincipal) {
    int atChar = bindPrincipal.lastIndexOf('@');

    if (atChar < 0) {
        logger.debug("User principal '" + bindPrincipal + "' does not contain the domain, and no domain has been configured");
        throw badCredentials();
    }

    return rootDnFromDomain(bindPrincipal.substring(atChar+ 1, bindPrincipal.length()));
}

private String rootDnFromDomain(String domain) {
    String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
    StringBuilder root = new StringBuilder();

    for (String token : tokens) {
        if (root.length() > 0) {
            root.append(',');
        }
        root.append("dc=").append(token);
    }

    return root.toString();
}

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

/**
 * By default, a failed authentication (LDAP error 49) will result in a {@code BadCredentialsException}.
 * <p>
 * If this property is set to {@code true}, the exception message from a failed bind attempt will be parsed
 * for the AD-specific error code and a {@link CredentialsExpiredException}, {@link DisabledException},
 * {@link AccountExpiredException} or {@link LockedException} will be thrown for the corresponding codes. All
 * other codes will result in the default {@code BadCredentialsException}.
 *
 * @param convertSubErrorCodesToExceptions {@code true} to raise an exception based on the AD error code.
 */
public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToExceptions) {
    this.convertSubErrorCodesToExceptions = convertSubErrorCodesToExceptions;
}

static class ContextFactory {
    DirContext createContext(Hashtable<?,?> env) throws NamingException {
        return new InitialLdapContext(env, null);
    }
}
}

ActiveDirectoryGrantedAuthoritiesMapper.java: (see my comment about the Active Directory group that must exist for the value given to ROLE_ADMIN. Also ROLE_ADMIN MUST be referenced in the spring-security-context.xml file in the security:intercept-url tag) ActiveDirectoryGrantedAuthoritiesMapper.java :(请参阅我对有关ROLE_ADMIN的值必须存在的Active Directory组的评论。还必须在security:intercept-url标记的spring-security-context.xml文件中引用ROLE_ADMIN)

package com.mycompany.pima.security;

import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;

public class ActiveDirectoryGrantedAuthoritiesMapper implements GrantedAuthoritiesMapper {

// Constants for group defined in LDAP
// The string "ExtranetUsers" in the ROLE_ADMIN var below actually maps to the name
// of an Active Directory group.
private String ROLE_ADMIN = "ExtranetUsers";

private static final Logger logger = LoggerFactory.getLogger(ActiveDirectoryGrantedAuthoritiesMapper.class);

public ActiveDirectoryGrantedAuthoritiesMapper() {

}

@Override
public Collection<? extends GrantedAuthority> mapAuthorities(
        final Collection<? extends GrantedAuthority> authorities) {

    Set<SecurityContextAuthority> roles = EnumSet.noneOf(SecurityContextAuthority.class);

    for (GrantedAuthority authority : authorities) {

        // authority.getAuthority() returns the role in LDAP nomenclature
        if (ROLE_ADMIN.equals(authority.getAuthority())) {
            roles.add(SecurityContextAuthority.ROLE_ADMIN);
        }
    }
    return roles;
}
 }

ActiveDirectoryAuthenticationException.java: ActiveDirectoryAuthenticationException.java:

package com.mycompany.pima.security;

import org.springframework.security.core.AuthenticationException;

@SuppressWarnings("serial")
public final class ActiveDirectoryAuthenticationException extends AuthenticationException {
private final String dataCode;

ActiveDirectoryAuthenticationException(String dataCode, String message, Throwable cause) {
    super(message, cause);
    this.dataCode = dataCode;
}

public String getDataCode() {
    return dataCode;
}
}

SecurityContextAuthority.java: SecurityContextAuthority.java:

package com.mycompany.pima.security;

import org.springframework.security.core.GrantedAuthority;

public enum SecurityContextAuthority implements GrantedAuthority {

// These roles are specified in the security context (security.xml) and are
// mapped to LDAP roles by the ActiveDirectoryGrantedAuthoritiesMapper
ROLE_ADMIN;

@Override
public String getAuthority() {
    return name();
}   
}

Then I had to adjust my spring-security-context.xml file. 然后我不得不调整我的spring-security-context.xml文件。 Here is what it looks like now: 这是现在的样子:

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

<security:http pattern="/login" security="none" />
<security:http pattern="/logerror" security="none" />

<!-- LDAP server details -->
<security:authentication-manager>
    <security:authentication-provider ref="ldapActiveDirectoryAuthProvider" />
</security:authentication-manager>

<beans:bean id="grantedAuthoritiesMapper" class="com.mycompany.pima.security.ActiveDirectoryGrantedAuthoritiesMapper"/>

<beans:bean id="ldapActiveDirectoryAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
    <beans:constructor-arg value="addomain.mycompany.com" />
    <beans:constructor-arg value="ldap://dev_ad_system.addomain.mycompany.com:389/" />
    <beans:property name="authoritiesMapper" ref="grantedAuthoritiesMapper" />
    <beans:property name="useAuthenticationRequestCredentials" value="true" />
    <beans:property name="convertSubErrorCodesToExceptions" value="true" />
</beans:bean>

<security:http auto-config="true" pattern="/**">
    <!-- Login pages -->
    <security:form-login login-page="/login" default-target-url="/users"
        login-processing-url="/j_spring_security_check" authentication-failure-url="/login?error=true" />
    <security:logout logout-success-url="/login"/>

       <!-- Security zones -->
       <!-- ROLE_ADMIN mentioned in the line below must match the name of the constant in the ActiveDirectoryGrantedAuthoritiesMapper.java file. -->
    <security:intercept-url pattern="/**" access="ROLE_ADMIN" />

</security:http>

</beans:beans>

I hope this helps someone, as I know how complicated and frustrating this can be. 我希望这有助于某人,因为我知道这可能是多么复杂和令人沮丧。

Thanks! 谢谢!

-Stephen Spalding - 斯蒂芬斯伯丁

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 通过Spring-boot Application通过Spring Security使用Active Directory(使用AD域)对用户进行身份验证时出现问题 - Issue while Authenticating user with Active Directory (using AD domain) using Spring Security from Spring-boot Application 使用 Spring Security 使用 Active Directory LDAP 进行身份验证时出现错误凭据 - Bad Credentials when authenticating with active directory LDAP with Spring Security 通过Kerberos使用Active Directory进行身份验证 - Authenticating with Active Directory via Kerberos Spring MVC不对数据库进行身份验证 - Spring MVC not authenticating versus database 在Linux上使用Java对Active Directory进行身份验证 - Authenticating against Active Directory with Java on Linux Java Spring Security-基于应用程序角色的Active Directory用户身份验证 - Java Spring Security - Application Role Based Active Directory User Authentication 使用Azure目录对托管Java应用程序进行身份验证 - Authenticating using Azure Directory for a Hosted Java Application Spring MVC应用程序中的活动MQ在JBoss部署中引发错误 - Active MQ in Spring MVC application throws error in JBoss deployment 使用活动目录自动验证 java 或 .net 应用程序中的用户 - Automatically authenticating a user in java or .net app using active directory 通过REST服务API调用以编程方式通过Active Directory验证用户 - Authenticating users via Active Directory programmatically on REST service API call
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM