使用LDAP配置RememberMe功能存在两个问题:
- 选择正确的RememberMe实现(令牌与PersistentTokens)
- 使用Spring的Java配置对其进行配置
我将逐步进行这些操作。
基于令牌的“记住我”功能(
TokenbasedRememberMeServices)在身份验证期间的工作方式如下:
- 用户获得身份验证(再次获得广告),我们目前知道用户的ID和密码
- 我们构造值username + expirationTime + password + staticKey并为其创建一个MD5哈希
- 我们创建一个包含用户名+到期时间+计算出的哈希值的cookie
当用户希望返回该服务并使用“记住我”功能进行身份验证时,我们:
- 检查cookie是否存在且未过期
- 从cookie中填充用户ID,然后调用提供的UserDetailsService,该服务应返回与用户ID相关的信息, 包括密码
- 然后,我们根据返回的数据计算哈希值,并验证cookie中的哈希值是否与我们计算出的值匹配
- 如果匹配,我们返回用户的Authentication对象
需要进行哈希检查过程,以确保没有人可以创建“伪造的”记住我的cookie,这将使他们冒充其他用户。问题在于,此过程依赖于从我们的存储库加载密码的可能性-
但这对于Active Directory是不可能的-我们无法基于用户名加载纯文本密码。
这使得基于令牌的实现不适合与AD配合使用(除非我们开始创建一些本地用户存储,其中包含密码或其他一些基于用户的秘密凭证,并且我不建议这种方法,因为我不知道您的应用程序,尽管这可能是一个好方法)。
另一个让我记住的实现是基于持久性令牌(
PersistentTokenbasedRememberMeServices)的,它的工作方式如下(以某种简化的方式):
- 当用户进行身份验证时,我们会生成一个随机令牌
- 我们将令牌与有关的用户ID信息一起存储在存储器中
- 我们创建一个包含令牌ID的cookie
当用户想要认证时,我们:
- 检查我们是否具有带有令牌ID的cookie
- 验证令牌ID是否存在于数据库中
- 根据数据库中的信息加载用户数据
如您所见,虽然现在我们需要令牌存储(通常是数据库,我们可以使用内存进行测试),但不再需要密码,而无需使用密码验证。
这使我们进入了配置部分。基于持久令牌的基本配置请记住我,如下所示:
@Overrideprotected void configure(HttpSecurity http) throws Exception { .... String internalSecretKey = "internalSecretKey"; http.rememberMe().rememberMeServices(rememberMeServices(internalSecretKey)).key(internalSecretKey);} @Bean public RememberMeServices rememberMeServices(String internalSecretKey) { BasicRememberMeUserDetailsService rememberMeUserDetailsService = new BasicRememberMeUserDetailsService(); InMemoryTokenRepositoryImpl rememberMeTokenRepository = new InMemoryTokenRepositoryImpl(); PersistentTokenbasedRememberMeServices services = new PersistentTokenbasedRememberMeServices(staticKey, rememberMeUserDetailsService, rememberMeTokenRepository); services.setAlwaysRemember(true); return services; }此实现将使用内存令牌存储,应将其替换
JdbcTokenRepositoryImpl为生产用。提供的
UserDetailsService内容负责为用户加载其他数据,这些数据由从“记住我”cookie加载的用户ID标识。最简单的实现如下所示:
public class BasicRememberMeUserDetailsService implements UserDetailsService { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return new User(username, "", Collections.<GrantedAuthority>emptyList()); }}您还可以提供另一个
UserDetailsService实现,根据需要从AD或内部数据库中加载其他属性或组成员身份。它可能看起来像这样:
@Beanpublic RememberMeServices rememberMeServices(String internalSecretKey) { LdapContextSource ldapContext = getLdapContext(); String searchbase = "OU=Users,DC=test,DC=company,DC=com"; String searchFilter = "(&(objectClass=user)(sAMAccountName={0}))"; FilterbasedLdapUserSearch search = new FilterbasedLdapUserSearch(searchbase, searchFilter, ldapContext); search.setSearchSubtree(true); LdapUserDetailsService rememberMeUserDetailsService = new LdapUserDetailsService(search); rememberMeUserDetailsService.setUserDetailsMapper(new CustomUserDetailsServiceImpl()); InMemoryTokenRepositoryImpl rememberMeTokenRepository = new InMemoryTokenRepositoryImpl(); PersistentTokenbasedRememberMeServices services = new PersistentTokenbasedRememberMeServices(internalSecretKey, rememberMeUserDetailsService, rememberMeTokenRepository); services.setAlwaysRemember(true); return services;}@Beanpublic LdapContextSource getLdapContext() { LdapContextSource source = new LdapContextSource(); source.setUserDn("user@"+DOMAIN); source.setPassword("password"); source.setUrl(URL); return source;}这会让您记住我的功能,该功能可与LDAP一起使用,并提供内部加载的数据,
RememberMeAuthenticationToken这些数据将在中提供
SecurityContextHolder.getContext().getAuthentication()。它还将能够重新使用您现有的逻辑来将LDAP数据解析为用户对象(
CustomUserDetailsServiceImpl)。
作为单独的主题,问题中发布的代码也存在一个问题,您应该替换以下内容:
authManagerBuilder .authenticationProvider(activeDirectoryLdapAuthenticationProvider()) .userDetailsService(userDetailsService()) ;
与:
authManagerBuilder .authenticationProvider(activeDirectoryLdapAuthenticationProvider()) ;
仅在添加基于DAO的身份验证(例如针对数据库)时才应调用userDetailsService,并且应使用用户详细信息服务的实际实现来调用。您当前的配置可能导致无限循环。



