我正在使用Spring Boot构建与LDAP集成的应用程序。我能够成功连接到LDAP服务器并验证用户身份。现在,我需要添加“记住我”功能。我试图浏览不同的帖子(this),但是找不到我的问题的答案。Spring Security官方文档指出
如果您使用的身份验证提供程序不使用UserDetailsService(例如LDAP提供程序),那么它将不起作用,除非您在应用程序上下文中还具有UserDetailsService Bean
这是我的工作代码,带有一些最初的想法,可以添加“记住我”功能:
Web安全配置
import com.ui.security.CustomUserDetailsServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.event.LoggerListener; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider; import org.springframework.security.ldap.userdetails.UserDetailsContextMapper; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { String DOMAIN = "ldap-server.com"; String URL = "ldap://ds.ldap-server.com:389"; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/ui/**").authenticated() .antMatchers("/", "/home", "/UIDL/**", "/ui/**").permitAll() .anyRequest().authenticated() ; http .formLogin() .loginPage("/login").failureUrl("/login?error=true").permitAll() .and().logout().permitAll() ; // Not sure how to implement this http.rememberMe().rememberMeServices(rememberMeServices()).key("password"); } @Override protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception { authManagerBuilder .authenticationProvider(activeDirectoryLdapAuthenticationProvider()) .userDetailsService(userDetailsService()) ; } @Bean public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() { ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(DOMAIN, URL); provider.setConvertSubErrorCodesToExceptions(true); provider.setUseAuthenticationRequestCredentials(true); provider.setUserDetailsContextMapper(userDetailsContextMapper()); return provider; } @Bean public UserDetailsContextMapper userDetailsContextMapper() { UserDetailsContextMapper contextMapper = new CustomUserDetailsServiceImpl(); return contextMapper; } /** * Impl of remember me service * @return */ @Bean public RememberMeServices rememberMeServices() { // TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("password", userService); // rememberMeServices.setCookieName("cookieName"); // rememberMeServices.setParameter("rememberMe"); return rememberMeServices; } @Bean public LoggerListener loggerListener() { return new LoggerListener(); } }
CustomUserDetailsServiceImpl
public class CustomUserDetailsServiceImpl implements UserDetailsContextMapper { @Autowired SecurityHelper securityHelper; Log ___log = LogFactory.getLog(this.getClass()); @Override public LoggedInUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> grantedAuthorities) { LoggedInUserDetails userDetails = null; try { userDetails = securityHelper.authenticateUser(ctx, username, grantedAuthorities); } catch (NamingException e) { e.printStackTrace(); } return userDetails; } @Override public void mapUserToContext(UserDetails user, DirContextAdapter ctx) { } }
我知道我需要以某种方式实现UserService,但不确定如何实现。
使用LDAP配置RememberMe功能存在两个问题:
我将逐步进行这些操作。
基于令牌的“记住我”功能(TokenBasedRememberMeServices)在身份验证期间的工作方式如下:
TokenBasedRememberMeServices
当用户希望返回该服务并使用“记住我”功能进行身份验证时,我们:
需要进行哈希检查过程,以确保没有人可以创建“伪造的”记住我的cookie,这将使他们冒充其他用户。问题在于,此过程依赖于从我们的存储库加载密码的可能性- 但这对于Active Directory是不可能的-我们无法基于用户名加载纯文本密码。
这使得基于令牌的实现不适合与AD配合使用(除非我们开始创建一些本地用户存储,其中包含密码或其他一些基于用户的秘密凭证,并且我不建议这种方法,因为我不知道您的应用程序,尽管这可能是一个好方法)。
另一个让我记住的实现是基于持久性令牌(PersistentTokenBasedRememberMeServices)的,它的工作方式如下(以某种简化的方式):
PersistentTokenBasedRememberMeServices
当用户想要认证时,我们:
如您所见,虽然现在我们需要令牌存储(通常是数据库,我们可以使用内存进行测试),但不再需要密码,而无需使用密码验证。
这使我们进入了配置部分。基于持久令牌的基本配置请记住我,如下所示:
@Override protected 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标识。最简单的实现如下所示:
JdbcTokenRepositoryImpl
UserDetailsService
public class BasicRememberMeUserDetailsService implements UserDetailsService { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return new User(username, "", Collections.<GrantedAuthority>emptyList()); } }
您还可以提供另一个UserDetailsService实现,根据需要从AD或内部数据库中加载其他属性或组成员身份。它可能看起来像这样:
@Bean public 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; } @Bean public 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)。
RememberMeAuthenticationToken
SecurityContextHolder.getContext().getAuthentication()
CustomUserDetailsServiceImpl
作为单独的主题,问题中发布的代码也存在一个问题,您应该替换以下内容:
authManagerBuilder .authenticationProvider(activeDirectoryLdapAuthenticationProvider()) .userDetailsService(userDetailsService()) ;
与:
authManagerBuilder .authenticationProvider(activeDirectoryLdapAuthenticationProvider()) ;
仅在添加基于DAO的身份验证(例如针对数据库)时才应调用userDetailsService,并且应使用用户详细信息服务的实际实现来调用。您当前的配置可能导致无限循环。