我正在使用下一个配置连接到我的AD:
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<beans:constructor-arg value="mydomain" />
<beans:constructor-arg value="ldap://my URL :389" />
<beans:property name="convertSubErrorCodesToExceptions" value="true"/>
</beans:bean>
连接工作正常,因为如果我写错了登录名/密码,我会收到“错误的凭据”(目录中找不到用户)
但如果我尝试使用正确的登录名和密码,我会得到一个例外:
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)
....
答案 0 :(得分:3)
检查所使用的搜索过滤器是否与您的活动目录记录一致。
我最近在我的网络应用中遇到了同样的异常。用户凭据正确,ActiveDirectoryLdapAuthenticationProvider正确绑定/验证。在搜索经过身份验证的记录的组和其他属性时绑定后发生故障。
如果查看ActiveDirectoryLdapAuthenticationProvider中的代码,它会为搜索过滤器提供硬编码值,并且始终使用绑定主体进行搜索。
这种方法
private DirContextOperations searchForUser(DirContext ctx, String username) throws NamingException {
SearchControls searchCtls = new SearchControls();
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
final String bindPrincipal = createBindPrincipal(username);
String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal);
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx, searchCtls, searchRoot, searchFilter,
new Object[]{bindPrincipal});
}
已提交Jira issue且已有补丁。
答案 1 :(得分:2)
错误IncorrectResultSizeDataAccessException是由org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl
中的错误引起的如果查看以下代码,当令牌seriesId不存在时,不应抛出错误“多个值”。
public PersistentRememberMeToken getTokenForSeries(String seriesId) {
try {
return (PersistentRememberMeToken) tokensBySeriesMapping.findObject(seriesId);
} catch(IncorrectResultSizeDataAccessException moreThanOne) {
logger.error("Querying token for series '" + seriesId + "' returned more than one value. Series" +
" should be unique");
} catch(DataAccessException e) {
logger.error("Failed to load token for series " + seriesId, e);
}
return null;
}
您可以实现自己的令牌库dao,这是我的:
/**
* Save/cache the login token, retrieve or update it for remember-me feature.
*
* create table persistent_logins (username varchar(64) not null, series varchar(64) primary key,
* token varchar(64) not null, last_used timestamp not null)
*
* @author lchen
*
*/
public class TokenRepositoryDao extends BaseDao implements PersistentTokenRepository {
@Override
public void createNewToken(PersistentRememberMeToken token) {
String sql = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";
getJdbcTemplate().update(sql, token.getUsername(), token.getSeries(), token.getTokenValue(), token.getDate());
}
@Override
public PersistentRememberMeToken getTokenForSeries(String series) {
String sql = "select username,series,token,last_used from persistent_logins where series = ?";
try {
return getJdbcTemplate().queryForObject(sql, new PersistentRememberMeTokenMapper(), series);
} catch (IncorrectResultSizeDataAccessException moreThanOne) {
if (moreThanOne.getActualSize() > 1)
logger.error("Querying token for series '" + series + "' returned more than one value. Series" + " should be unique");
} catch (DataAccessException e) {
logger.error("Failed to load token for series " + series, e);
}
return null;
}
@Override
public void removeUserTokens(String username) {
String sql = "delete from persistent_logins where username = ?";
getJdbcTemplate().update(sql, username);
}
@Override
public void updateToken(String series, String tokenValue, Date lastUsed) {
String sql = "update persistent_logins set token = ?, last_used = ? where series = ?";
getJdbcTemplate().update(sql, tokenValue, new Date(), series);
}
private class PersistentRememberMeTokenMapper implements RowMapper<PersistentRememberMeToken> {
@Override
public PersistentRememberMeToken mapRow(ResultSet rs, int rowNum) throws SQLException {
String username = rs.getString("username");
String series = rs.getString("series");
String token = rs.getString("token");
Date date = rs.getDate("last_used");
return new PersistentRememberMeToken(username, series, token, date);
}
}
}
以下是弹簧安全的可行配置:
<security:http pattern="/common/**" security="none" />
<security:http pattern="/styles/**" security="none" />
<security:http pattern="/images/**" security="none" />
<security:http pattern="/scripts/**" security="none" />
<security:http pattern="/layouts/**" security="none" />
<security:http use-expressions="true">
<security:intercept-url pattern="/login.do" access="permitAll" />
<security:intercept-url pattern="/logout.do" access="permitAll" />
<security:intercept-url pattern="/login/failure.do" access="permitAll" />
<security:intercept-url pattern="/index.jsp" access="permitAll" />
<security:intercept-url pattern="/home/**" access="isAuthenticated()" />
<security:intercept-url pattern="/upload/**" access="hasRole('ROLE_USER')" />
<security:intercept-url pattern="/**" access="denyAll" />
<security:form-login login-page="/login.do" authentication-failure-url="/login/failure.do" default-target-url="/" />
<security:logout logout-url="/logout.do" logout-success-url="/" delete-cookies="JSESSIONID" />
<security:remember-me user-service-ref="userDetailsService" token-repository-ref="tokenRepository" token-validity-seconds="1296000" />
</security:http>
<bean id="tokenRepository" class="com.abc.dao.TokenRepositoryDao" />
<security:authentication-manager>
<security:authentication-provider ref="ldapAuthProvider" />
</security:authentication-manager>
<bean id="userDetailsService" class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
<constructor-arg ref="userSearch" />
<constructor-arg ref="authoritiesPopulator" />
</bean>
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldap://corp.abc.com:389/dc=Corp,dc=abc,dc=com" />
<property name="userDn" value="***" />
<property name="password" value="***" />
<property name="baseEnvironmentProperties">
<map>
<entry key="java.naming.referral">
<value>follow</value> <!-- Avoid error: Unprocessed Continuation Reference(s); remaining name '' -->
</entry>
</map>
</property>
</bean>
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg>
<value></value> <!-- blank value is required here! -->
</constructor-arg>
<constructor-arg>
<value>(sAMAccountName={0})</value>
</constructor-arg>
<constructor-arg ref="contextSource" />
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
<bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg ref="authenticator" />
<constructor-arg ref="authoritiesPopulator" />
</bean>
<bean id="authenticator" class="org.springframework.security.ldap.authentication.BindAuthenticator">
<constructor-arg ref="contextSource" />
<property name="userDnPatterns">
<list>
<value>sAMAccountName={0}</value>
</list>
</property>
<property name="userSearch" ref="userSearch" />
</bean>
<bean id="authoritiesPopulator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<constructor-arg ref="contextSource" />
<constructor-arg value="" /> <!-- From the root DN of the context factory -->
<property name="groupRoleAttribute" value="cn" />
<property name="rolePrefix" value="ROLE_" />
<property name="searchSubtree" value="true" />
<property name="convertToUpperCase" value="true" />
<property name="ignorePartialResultException">
<value>false</value>
</property>
</bean>
答案 2 :(得分:1)
我在尝试对Active Directory进行身份验证时遇到了同样的问题IncorrectResultSizeDataAccessException
。我还没有直接解决这个问题,但我已经实现了一个功能齐全的解决方法,但这意味着您需要有一个“服务帐户”用户名和密码才能与AD建立通信。我猜它使用的是“通用”Spring LDAP方法,而不是特殊的AD方法。
我按照这里的食谱:
Active Directory Spring Security XML config, on the SpringSource forum
这是我的security-context.xml
文件,供参考:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
">
<!-- There is some Java based config here, don't forget. -->
<!-- Its not important for this example-->
<context:component-scan base-package="uk.ac.example.ldaptest.security" />
<!-- This is for our Active Dir LDAP implementation -->
<beans:bean id="contextSource"
class="org.springframework.ldap.core.support.LdapContextSource">
<beans:property name="url"
value="LDAP://ads.ntd.example.ac.uk:389" />
<beans:property name="base" value="dc=ntd,dc=example,dc=ac,dc=uk" />
<beans:property name="userDn" value="cn=ldap,ou=Service Accounts,ou=Management,ou=example,dc=ntd,dc=example,dc=ac,dc=uk" />
<beans:property name="password" value="XXXXXXXXX" />
<beans:property name="pooled" value="true" />
<!-- AD Specific Setting for avoiding the partial exception error -->
<beans:property name="referral" value="follow" />
</beans:bean>
<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:property name="userSearch">
<beans:bean id="userSearch"
class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<beans:constructor-arg index="0" value="" />
<beans:constructor-arg index="1" value="(sAMAccountName={0})" />
<beans:constructor-arg index="2" ref="contextSource" />
</beans:bean>
</beans:property>
</beans:bean>
</beans:constructor-arg>
<beans:constructor-arg>
<beans:bean
class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<beans:constructor-arg ref="contextSource" />
<beans:constructor-arg value="" />
<beans:property name="groupSearchFilter" value="(member={0})" />
<beans:property name="searchSubtree" value="true" />
<!-- Below Settings convert the adds the prefix ROLE_ to roles returned
from AD -->
</beans:bean>
</beans:constructor-arg>
<!-- Create the Mapper object that returns our customised User object -->
<!-- Set up in the Java based config mentioned earlier -->
<beans:property name="userDetailsContextMapper" ref="myUdcm" />
</beans:bean>
<beans:bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<beans:constructor-arg>
<beans:list>
<beans:ref local="ldapAuthenticationProvider" />
</beans:list>
</beans:constructor-arg>
</beans:bean>
<!-- we want all URLs within our application to be secured, requiring the
role ROLE_STAFF to access them. LDAP supplies this -->
<http auto-config="true" use-expressions="true"
authentication-manager-ref="authenticationManager">
<intercept-url pattern="/resources/**" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_STAFF')" />
<session-management>
<concurrency-control max-sessions="1" />
</session-management>
</http>