24

मैं वसंत बूट के साथ एक ऐप बना रहा हूं जिसमें एलडीएपी के साथ एकीकरण है। मैं सफलतापूर्वक एलडीएपी सर्वर से कनेक्ट करने और उपयोगकर्ता को प्रमाणीकृत करने में सक्षम था। अब मुझे याद रखने की कार्यक्षमता जोड़ने की आवश्यकता है। मैंने विभिन्न पदों (this) को देखने की कोशिश की लेकिन मेरी समस्या का उत्तर नहीं मिला। आधिकारिक स्प्रिंग सुरक्षा document कहा गया है किवसंत सुरक्षा एलडीएपी और मुझे याद रखें

आप एक प्रमाणीकरण प्रदाता जो एक UserDetailsService का उपयोग नहीं करता उपयोग कर रहे हैं (उदाहरण के लिए, LDAP प्रदाता) तो यह काम नहीं करेगा जब तक कि आप भी इसमें UserDetailsService सेम है आपके आवेदन संदर्भ

यहाँ कुछ प्रारंभिक विचारों के साथ मेरे कार्य कोड जोड़ने के लिए याद-मुझे कार्यक्षमता:

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 लागू करने की आवश्यकता है, लेकिन यकीन नहीं है कि कैसे प्राप्त किया जा सकता।

उत्तर

26

वहाँ RememberMe के विन्यास के लिए दो मुद्दे हैं एलडीएपी के साथ विशेषताएं:: सही RememberMe कार्यान्वयन के

  • चयन (टोकन बनाम PersistentTokens)
  • फिर अपने config कुछ इस तरह दिखेगा स्प्रिंग के जावा कॉन्फ़िगरेशन

का उपयोग करके इसकी कॉन्फ़िगरेशन मैं इन चरणों को चरणबद्ध रूप से ले जाऊंगा।

टोकन आधारित मुझे याद सुविधा (TokenBasedRememberMeServices) प्रमाणीकरण के दौरान निम्नलिखित तरीके से काम करता है:

  • उपयोगकर्ता प्रमाणीकृत हो जाता है (ई agaisnt) और हम वर्तमान में उपयोगकर्ता के आईडी और पासवर्ड
  • हम मूल्य उपयोगकर्ता नाम का निर्माण पता + expirationTime + पासवर्ड + staticKey और
  • हम एक कुकी जो उपयोगकर्ता नाम + समाप्ति + परिकलित हैश बनाने के लिए यह एक MD5 हैश बनाने

उपयोगकर्ता सेवा के लिए वापस आने के लिए और मुझे याद कार्यक्षमता का उपयोग प्रमाणीकृत किया चाहता है जब हम:

  • कुकी मौजूद है और समाप्त नहीं हुआ है
  • कुकी से उपयोगकर्ता आईडी भरते हैं और कॉल प्रदान की जाँच करें कि क्या UserDetailsService जो उपयोगकर्ता के आईडी से संबंधित जानकारी के वापस जाने के लिए, पासवर्ड
  • हम तो दिए गए डेटा से हैश की गणना करने और सत्यापित करें कि कुकी में हैश मान हम गणना की
  • अगर यह मेल खाता के साथ मेल खाता है सहित की उम्मीद है हम वापस आते हैं उपयोगकर्ता के प्रमाणीकरण वस्तु

हैश जाँच प्रक्रिया आदेश सुनिश्चित करें कि कोई भी एक "नकली" मुझे याद रखें कुकी है, जो उन्हें एक और प्रयोक्ता का रूप धारण करते हैं बना सकते हैं बनाने के लिए आवश्यक है। समस्या यह है कि यह प्रक्रिया हमारे भंडार से पासवर्ड लोड करने की संभावना पर निर्भर करती है - लेकिन सक्रिय निर्देशिका के साथ यह असंभव है - हम उपयोगकर्ता नाम के आधार पर सादे टेक्स्ट पासवर्ड लोड नहीं कर सकते हैं।

यह टोकन-आधारित कार्यान्वयन को एडी के उपयोग के लिए अनुपयुक्त बनाता है (जब तक कि हम कुछ स्थानीय उपयोगकर्ता स्टोर बनाना शुरू नहीं करते हैं जिसमें पासवर्ड या कुछ अन्य गुप्त उपयोगकर्ता-आधारित क्रेडेंशियल होता है और मैं इस दृष्टिकोण का सुझाव नहीं दे रहा हूं क्योंकि मैं नहीं करता अपने आवेदन के अन्य विवरणों को जानें, हालांकि यह जाने का एक अच्छा तरीका हो सकता है)।

अन्य मुझे याद कार्यान्वयन लगातार टोकन (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 मुझे याद रखने वाली कुकी से लोड की गई उपयोगकर्ता आईडी द्वारा पहचाने गए उपयोगकर्ता के लिए अतिरिक्त डेटा लोड करने के लिए ज़िम्मेदार है। simpliest क्रियान्वयन ऐसा दिखाई दे सकते हैं:

    public class BasicRememberMeUserDetailsService implements UserDetailsService { 
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
         return new User(username, "", Collections.<GrantedAuthority>emptyList()); 
        } 
    } 
    

    तुम भी एक और UserDetailsService कार्यान्वयन जो आपके विज्ञापन या आंतरिक डेटाबेस से अतिरिक्त गुण या समूह सदस्यता लोड करता है, अपनी आवश्यकताओं पर निर्भर करता है आपूर्ति कर सकता। यह ऐसा दिखाई दे सकता:

    @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("[email protected]"+DOMAIN); 
        source.setPassword("password"); 
        source.setUrl(URL); 
        return source; 
    } 
    

    यह तुम मुझे जो एलडीएपी के साथ काम करता है और RememberMeAuthenticationToken अंदर भरी हुई डेटा जो SecurityContextHolder.getContext().getAuthentication() में उपलब्ध हो जाएगा प्रदान करता है कार्यक्षमता याद हो जाएगी। यह उपयोगकर्ता ऑब्जेक्ट (CustomUserDetailsServiceImpl) में एलडीएपी डेटा को पार्स करने के लिए आपके मौजूदा तर्क का फिर से उपयोग करने में सक्षम होगा।

    एक अलग विषय के रूप में, वहाँ भी है कोड प्रश्न पोस्ट करने वाले एक बात नहीं, आप बदलना चाहिए:

    authManagerBuilder 
          .authenticationProvider(activeDirectoryLdapAuthenticationProvider()) 
          .userDetailsService(userDetailsService()) 
        ; 
    

    साथ:

    authManagerBuilder 
          .authenticationProvider(activeDirectoryLdapAuthenticationProvider()) 
        ; 
    

    userDetailsService करने के लिए कॉल केवल किया जाना चाहिए डीएओ-आधारित प्रमाणीकरण (जैसे डेटाबेस के खिलाफ) जोड़ने के लिए और उपयोगकर्ता विवरण सेवा के वास्तविक कार्यान्वयन के साथ बुलाया जाना चाहिए। आपकी वर्तमान कॉन्फ़िगरेशन अनंत लूप का कारण बन सकती है।

  • +0

    अच्छा जवाब Vladimír! त्वरित प्रश्न। कोड की इस पंक्ति में: PersistentTokenBasedRememberMeServices सेवाएं = नया PersistentTokenBasedRememberMeServices (staticKey, RememberMeUserDetailsService, RememberMeTokenRepository); मुझे लगता है कि "staticKey" "internalSecretKey" जैसा ही है। क्या वो सही है? – Maksim

    +1

    हां, यह वही कुंजी है - इसका उपयोग यह सत्यापित करने के लिए किया जाता है कि RememberMe प्रमाणीकरण टोकन को इस रहस्य के ज्ञान के साथ बनाया गया था और RememberMe प्रमाणीकरण प्रदाता में सत्यापित किया गया था। सुनिश्चित करें कि आप मेरे उत्तर के नवीनतम संस्करण का उपयोग कर रहे हैं, क्योंकि उपर्युक्त वर्तमान पाठ में इसे सभी जगहों पर "आंतरिक सुरक्षा" के रूप में जाना जाता है। –

    +0

    बढ़िया, बहुत बहुत धन्यवाद! सब कुछ काम करता है! – Maksim

    0

    ऐसा लगता है कि आप UserService का एक उदाहरण खो रहे हैं कि आपके RememberMeService को संदर्भ के लिए आवश्यक है। चूंकि आप एलडीएपी का उपयोग कर रहे हैं, इसलिए आपको UserService का एलडीएपी संस्करण चाहिए। मैं केवल जेडीबीसी/जेपीए कार्यान्वयन से परिचित हूं, लेकिन ऐसा लगता है कि 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; 
    } 
    
    संबंधित मुद्दे