我正在使用Spring Boot构建一个与LDAP集成的应用程序。我能够成功连接到LDAP服务器并验证用户身份。现在我需要添加remember-me功能。我试着查看不同的帖子(this),但无法找到问题的答案。官方春季安全document声明
如果您使用的身份验证提供程序不使用 UserDetailsService(例如,LDAP提供程序)然后它将无法工作 除非您的应用程序中还有一个UserDetailsService bean 上下文
这里是我的工作代码,其中包含一些初步想法,以添加记住我的功能:
WebSecurityConfig
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,但不确定如何实现。
答案 0 :(得分:28)
使用LDAP配置RememberMe功能有两个问题:
我将逐步采取这些措施。
基于令牌的记住我功能(TokenBasedRememberMeServices
)在身份验证期间以下列方式工作:
当用户想要回到服务并使用“记住我”功能进行身份验证时,我们:
哈希检查过程是必需的,以确保没有人可以创建&#34;假的&#34;记住我的cookie,这会让他们模仿另一个用户。问题是这个过程依赖于从我们的存储库加载密码的可能性 - 但是使用Active Directory是不可能的 - 我们无法根据用户名加载明文密码。
这使得基于令牌的实现不适合与AD一起使用(除非我们开始创建包含密码或其他一些基于用户的秘密凭证的本地用户存储,并且我并没有建议这种方法,因为我没有&# 39;不知道你的申请的其他细节,虽然这可能是一个很好的方式)。
另一个记得我实现是基于持久性令牌(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标识的用户加载其他数据。最简单的实现可能如下所示:
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数据解析为User对象(CustomUserDetailsServiceImpl
)。
作为一个单独的主题,问题中发布的代码也有一个问题,你应该替换:
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService())
;
使用:
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
;
只应对userDetailsService进行调用,以便添加基于DAO的身份验证(例如,针对数据库),并且应该使用用户详细信息服务的实际实现来调用。您当前的配置可能导致无限循环。
答案 1 :(得分:0)
听起来你错过了UserService
RememberMeService
需要引用的实例。由于您使用的是LDAP,因此您需要一个UserService
的LDAP版本。我只熟悉JDBC / JPA实现,但看起来像org.springframework.security.ldap.userdetails.LdapUserDetailsManager
正是您正在寻找的。然后你的配置看起来像这样:
@Bean
public UserDetailsService getUserDetailsService() {
return new LdapUserDetailsManager(); // TODO give it whatever constructor params it needs
}
@Bean
public RememberMeServices rememberMeServices() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("password", getUserDetailsService());
rememberMeServices.setCookieName("cookieName");
rememberMeServices.setParameter("rememberMe");
return rememberMeServices;
}