簡體   English   中英

Spring Security LDAP和Remember Me

[英]Spring Security LDAP and Remember Me

我正在使用Spring Boot構建一個與LDAP集成的應用程序。 我能夠成功連接到LDAP服務器並驗證用戶身份。 現在我需要添加remember-me功能。 我試圖通過不同的帖子( 這個 )查看,但無法找到我的問題的答案。 Spring Spring官方文件指出

如果您使用的身份驗證提供程序不使用UserDetailsS​​ervice(例如,LDAP提供程序),那么除非您的應用程序上下文中還有UserDetailsS​​ervice 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();
    }
}

CustomUserDetailsS​​erviceImpl

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功能有兩個問題:

  • 選擇正確的RememberMe實現(Tokens vs. PersistentTokens)
  • 它使用Spring的Java配置進行配置

我將逐步采取這些措施。

基於令牌的記住我功能( TokenBasedRememberMeServices )在身份驗證期間以下列方式工作:

  • 用戶獲得身份驗證(agaisnt AD),我們目前知道用戶的ID和密碼
  • 我們構造值username + expirationTime + password + staticKey並創建它的MD5哈希值
  • 我們創建一個cookie,其中包含用戶名+到期+計算的哈希值

當用戶想要回到服務並使用記住我的功能進行身份驗證時,我們:

  • 檢查cookie是否存在且未過期
  • 從cookie中填充用戶ID並調用提供的UserDetailsS​​ervice,該服務有望返回與用戶ID相關的信息, 包括密碼
  • 然后,我們根據返回的數據計算哈希值,並驗證cookie中的哈希值是否與我們計算的值匹配
  • 如果匹配,我們返回用戶的Authentication對象

哈希檢查過程是必需的,以確保沒有人可以創建一個“假”記住我的cookie,這將讓他們模仿另一個用戶。 問題是這個過程依賴於從我們的存儲庫加載密碼的可能性 - 但是使用Active Directory這是不可能的 - 我們無法根據用戶名加載明文密碼。

這使得基於令牌的實現不適合與AD一起使用(除非我們開始創建一些包含密碼或其他一些基於用戶的秘密憑證的本地用戶存儲,並且我不建議這種方法,因為我不知道其他細節你的申請,雖然這可能是一個很好的方式)。

另一個記住我的實現基於持久性令牌( PersistentTokenBasedRememberMeServices ),它的工作原理如下(以簡化的方式):

  • 當用戶驗證時,我們生成一個隨機令牌
  • 我們將令牌存儲在存儲器中,並附帶有關與其關聯的用戶ID的信息
  • 我們創建一個包含令牌ID的cookie

當用戶想要驗證我們時:

  • 檢查我們是否有可用令牌ID的cookie
  • 驗證數據庫中是否存在令牌ID
  • 根據數據庫中的信息加載用戶的數據

正如您所看到的,不再需要密碼,盡管我們現在需要一個令牌存儲(通常是數據庫,我們可以使用內存進行測試),而不是使用密碼驗證。

這讓我們進入了配置部分。 基於持久令牌的基本配置記住我看起來像這樣:

@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())
    ;

只應對userDetailsS​​ervice進行調用,以便添加基於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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM