![](/img/trans.png)
[英]How do I use “Remember Me” authentication with Spring Security and LDAP?
[英]Spring Security LDAP and Remember Me
我正在使用Spring Boot構建一個與LDAP集成的應用程序。 我能夠成功連接到LDAP服務器並驗證用戶身份。 現在我需要添加remember-me功能。 我試圖通過不同的帖子( 這個 )查看,但無法找到我的問題的答案。 Spring Spring官方文件指出
如果您使用的身份驗證提供程序不使用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,但不確定如何實現。
使用LDAP配置RememberMe功能有兩個問題:
我將逐步采取這些措施。
基於令牌的記住我功能( TokenBasedRememberMeServices
)在身份驗證期間以下列方式工作:
當用戶想要回到服務並使用記住我的功能進行身份驗證時,我們:
哈希檢查過程是必需的,以確保沒有人可以創建一個“假”記住我的cookie,這將讓他們模仿另一個用戶。 問題是這個過程依賴於從我們的存儲庫加載密碼的可能性 - 但是使用Active Directory這是不可能的 - 我們無法根據用戶名加載明文密碼。
這使得基於令牌的實現不適合與AD一起使用(除非我們開始創建一些包含密碼或其他一些基於用戶的秘密憑證的本地用戶存儲,並且我不建議這種方法,因為我不知道其他細節你的申請,雖然這可能是一個很好的方式)。
另一個記住我的實現基於持久性令牌( 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的身份驗證(例如,針對數據庫),並且應該使用用戶詳細信息服務的實際實現來調用。 您當前的配置可能會導致無限循環。
聽起來您缺少一個您的RememberMeService
需要引用的UserService
實例。 由於您使用的是LDAP,因此需要LDAP版本的UserService
。 我只熟悉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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.