복붙노트

[SPRING] 봄 보안 LDAP 및 기억하기

SPRING

봄 보안 LDAP 및 기억하기

나는 LDAP와의 통합을 가지고있는 Spring Boot로 앱을 만들고있다. LDAP 서버에 성공적으로 연결하고 사용자를 인증 할 수있었습니다. 이제는 remember-me 기능을 추가해야합니다. 나는 다른 게시물 (이)을 보려고했지만 내 문제에 대한 답을 찾을 수 없었습니다. 공식적인 스프링 보안 문서에는

remember-me 기능을 추가하는 몇 가지 초기 생각을 가진 작업 코드는 다음과 같습니다.

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

사용자 정의 UserDetailsService Impl

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를 구현해야하지만 어떻게 달성 할 수 있는지 확신 할 수 없다는 것을 알고 있습니다.

해결법

  1. ==============================

    1.LDAP를 통한 RememberMe 기능의 구성에는 두 가지 문제가 있습니다.

    LDAP를 통한 RememberMe 기능의 구성에는 두 가지 문제가 있습니다.

    나는이 단계별로 조치를 취할 것이다.

    토큰 기반 기억 기능 (TokenBasedRememberMeServices)은 인증 과정에서 다음과 같은 방식으로 작동합니다.

    사용자가 서비스로 돌아가서 Remember Me 기능을 사용하여 인증 받기를 원할 때 :

    해시 검사 프로세스는 아무도 "가짜"기억 쿠키를 만들 수 없도록하여 다른 사용자를 가장 할 수있게하기 위해 필요합니다. 문제는이 프로세스가 저장소에서 비밀번호를로드 할 가능성에 의존한다는 것입니다.하지만 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는 Remember Me 쿠키에서로드 된 사용자 ID로 식별되는 사용자에 대한 추가 데이터를로드합니다. 가장 간단한 구현은 다음과 같습니다.

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

    필요에 따라 AD 또는 내부 데이터베이스에서 추가 속성이나 그룹 구성원을로드하는 다른 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("user@"+DOMAIN);
        source.setPassword("password");
        source.setUrl(URL);
        return source;
    }
    

    이렇게하면 LDAP와 함께 작동하고 SecurityContextHolder.getContext (). getAuthentication ()에서 사용할 수있는 RememberMeAuthenticationToken 내에로드 된 데이터를 제공하는 기능을 기억하게됩니다. 또한 LDAP 데이터를 사용자 개체 (CustomUserDetailsServiceImpl)로 구문 분석하기 위해 기존 논리를 다시 사용할 수 있습니다.

    별도의 주제로, 질문에 게시 된 코드에 한 가지 문제가 있습니다. 다음을 대체해야합니다.

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

    와:

        authManagerBuilder
                .authenticationProvider(activeDirectoryLdapAuthenticationProvider())
        ;
    

    userDetailsService에 대한 호출은 DAO 기반 인증을 추가하기 위해 (예 : 데이터베이스에 대해) 수행되어야하며 사용자 세부 정보 서비스의 실제 구현으로 호출해야합니다. 현재 구성으로 인해 무한 루프가 발생할 수 있습니다.

  2. ==============================

    2.RememberMeService에 대한 참조가 필요한 UserService의 인스턴스가 누락 된 것 같습니다. LDAP를 사용하고 있기 때문에 LDAP 버전의 UserService가 필요합니다. JDBC / JPA 구현에만 익숙하지만, org.springframework.security.ldap.userdetails.LdapUserDetailsManager는 사용자가 찾고있는 것입니다. 그렇다면 설정은 다음과 같습니다.

    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;
    }
    
  3. from https://stackoverflow.com/questions/24745528/spring-security-ldap-and-remember-me by cc-by-sa and MIT license