错误 IncorrectResultSizeDataAccessException 是由 org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl 中的错误引起的
如果查看以下代码,当 token 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);
}
}
}
以下是 Spring Security 的可行配置:
<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>