복붙노트

[SPRING] Spring Security를 ​​사용하여 Active Directory 서버에 대해 어떻게 인증합니까?

SPRING

Spring Security를 ​​사용하여 Active Directory 서버에 대해 어떻게 인증합니까?

나는 사용자가 로그인하도록 요구하는 Spring 웹 애플리케이션을 작성 중이다. 우리 회사는이 용도로 사용하고자하는 Active Directory 서버를 보유하고 있습니다. 그러나 스프링 보안을 사용하여 서버에 연결하는 데 문제가 있습니다.

저는 Spring 2.5.5와 Spring Security 2.0.3을 Java 1.6과 함께 사용하고 있습니다.

LDAP URL을 잘못된 IP 주소로 변경하면 예외가 발생하지 않으므로 서버에 연결하려고하는지 궁금합니다.

웹 응용 프로그램이 정상적으로 시작되지만 로그인 페이지에 입력 한 정보는 거부됩니다. 이전에 InMemoryDaoImpl을 사용 했으므로 제대로 작동 했으므로 나머지 응용 프로그램은 올바르게 구성되어있는 것 같습니다.

다음은 내 보안 관련 bean입니다.

  <beans:bean id="ldapAuthProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
    <beans:constructor-arg>
      <beans:bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
        <beans:constructor-arg ref="initialDirContextFactory" />
        <beans:property name="userDnPatterns">
          <beans:list>
            <beans:value>CN={0},OU=SBSUsers,OU=Users,OU=MyBusiness,DC=Acme,DC=com</beans:value>
          </beans:list>
        </beans:property>
      </beans:bean>
    </beans:constructor-arg>
  </beans:bean>

  <beans:bean id="userDetailsService" class="org.springframework.security.userdetails.ldap.LdapUserDetailsManager">
    <beans:constructor-arg ref="initialDirContextFactory" />
  </beans:bean>

  <beans:bean id="initialDirContextFactory" class="org.springframework.security.ldap.DefaultInitialDirContextFactory">
    <beans:constructor-arg value="ldap://192.168.123.456:389/DC=Acme,DC=com" />
  </beans:bean>

해결법

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

    1.내가했던 것과 똑같은 경험을했고 Active Directory 서버에 대한 LDAP 쿼리를 수행하는 사용자 지정 인증 공급자를 작성했습니다.

    내가했던 것과 똑같은 경험을했고 Active Directory 서버에 대한 LDAP 쿼리를 수행하는 사용자 지정 인증 공급자를 작성했습니다.

    보안 관련 콩은 다음과 같습니다.

    <beans:bean id="contextSource"
        class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
        <beans:constructor-arg value="ldap://hostname.queso.com:389/" />
    </beans:bean>
    
    <beans:bean id="ldapAuthenticationProvider"
        class="org.queso.ad.service.authentication.LdapAuthenticationProvider">
        <beans:property name="authenticator" ref="ldapAuthenticator" />
        <custom-authentication-provider />
    </beans:bean>
    
    <beans:bean id="ldapAuthenticator"
        class="org.queso.ad.service.authentication.LdapAuthenticatorImpl">
        <beans:property name="contextFactory" ref="contextSource" />
        <beans:property name="principalPrefix" value="QUESO\" />
    </beans:bean>
    

    그런 다음 LdapAuthenticationProvider 클래스

    /**
     * Custom Spring Security authentication provider which tries to bind to an LDAP server with
     * the passed-in credentials; of note, when used with the custom {@link LdapAuthenticatorImpl},
     * does <strong>not</strong> require an LDAP username and password for initial binding.
     * 
     * @author Jason
     */
    public class LdapAuthenticationProvider implements AuthenticationProvider {
    
        private LdapAuthenticator authenticator;
    
        public Authentication authenticate(Authentication auth) throws AuthenticationException {
    
            // Authenticate, using the passed-in credentials.
            DirContextOperations authAdapter = authenticator.authenticate(auth);
    
            // Creating an LdapAuthenticationToken (rather than using the existing Authentication
            // object) allows us to add the already-created LDAP context for our app to use later.
            LdapAuthenticationToken ldapAuth = new LdapAuthenticationToken(auth, "ROLE_USER");
            InitialLdapContext ldapContext = (InitialLdapContext) authAdapter
                    .getObjectAttribute("ldapContext");
            if (ldapContext != null) {
                ldapAuth.setContext(ldapContext);
            }
    
            return ldapAuth;
        }
    
        public boolean supports(Class clazz) {
            return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(clazz));
        }
    
        public LdapAuthenticator getAuthenticator() {
            return authenticator;
        }
    
        public void setAuthenticator(LdapAuthenticator authenticator) {
            this.authenticator = authenticator;
        }
    
    }
    

    그런 다음 LdapAuthenticator Impl 클래스 :

    /**
     * Custom Spring Security LDAP authenticator which tries to bind to an LDAP server using the
     * passed-in credentials; does <strong>not</strong> require "master" credentials for an
     * initial bind prior to searching for the passed-in username.
     * 
     * @author Jason
     */
    public class LdapAuthenticatorImpl implements LdapAuthenticator {
    
        private DefaultSpringSecurityContextSource contextFactory;
        private String principalPrefix = "";
    
        public DirContextOperations authenticate(Authentication authentication) {
    
            // Grab the username and password out of the authentication object.
            String principal = principalPrefix + authentication.getName();
            String password = "";
            if (authentication.getCredentials() != null) {
                password = authentication.getCredentials().toString();
            }
    
            // If we have a valid username and password, try to authenticate.
            if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
                InitialLdapContext ldapContext = (InitialLdapContext) contextFactory
                        .getReadWriteContext(principal, password);
    
                // We need to pass the context back out, so that the auth provider can add it to the
                // Authentication object.
                DirContextOperations authAdapter = new DirContextAdapter();
                authAdapter.addAttributeValue("ldapContext", ldapContext);
    
                return authAdapter;
            } else {
                throw new BadCredentialsException("Blank username and/or password!");
            }
        }
    
        /**
         * Since the InitialLdapContext that's stored as a property of an LdapAuthenticationToken is
         * transient (because it isn't Serializable), we need some way to recreate the
         * InitialLdapContext if it's null (e.g., if the LdapAuthenticationToken has been serialized
         * and deserialized). This is that mechanism.
         * 
         * @param authenticator
         *          the LdapAuthenticator instance from your application's context
         * @param auth
         *          the LdapAuthenticationToken in which to recreate the InitialLdapContext
         * @return
         */
        static public InitialLdapContext recreateLdapContext(LdapAuthenticator authenticator,
                LdapAuthenticationToken auth) {
            DirContextOperations authAdapter = authenticator.authenticate(auth);
            InitialLdapContext context = (InitialLdapContext) authAdapter
                    .getObjectAttribute("ldapContext");
            auth.setContext(context);
            return context;
        }
    
        public DefaultSpringSecurityContextSource getContextFactory() {
            return contextFactory;
        }
    
        /**
         * Set the context factory to use for generating a new LDAP context.
         * 
         * @param contextFactory
         */
        public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) {
            this.contextFactory = contextFactory;
        }
    
        public String getPrincipalPrefix() {
            return principalPrefix;
        }
    
        /**
         * Set the string to be prepended to all principal names prior to attempting authentication
         * against the LDAP server.  (For example, if the Active Directory wants the domain-name-plus
         * backslash prepended, use this.)
         * 
         * @param principalPrefix
         */
        public void setPrincipalPrefix(String principalPrefix) {
            if (principalPrefix != null) {
                this.principalPrefix = principalPrefix;
            } else {
                this.principalPrefix = "";
            }
        }
    
    }
    

    마지막으로 LdapAuthenticationToken 클래스는 다음과 같습니다.

    /**
     * <p>
     * Authentication token to use when an app needs further access to the LDAP context used to
     * authenticate the user.
     * </p>
     * 
     * <p>
     * When this is the Authentication object stored in the Spring Security context, an application
     * can retrieve the current LDAP context thusly:
     * </p>
     * 
     * <pre>
     * LdapAuthenticationToken ldapAuth = (LdapAuthenticationToken) SecurityContextHolder
     *      .getContext().getAuthentication();
     * InitialLdapContext ldapContext = ldapAuth.getContext();
     * </pre>
     * 
     * @author Jason
     * 
     */
    public class LdapAuthenticationToken extends AbstractAuthenticationToken {
    
        private static final long serialVersionUID = -5040340622950665401L;
    
        private Authentication auth;
        transient private InitialLdapContext context;
        private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
    
        /**
         * Construct a new LdapAuthenticationToken, using an existing Authentication object and
         * granting all users a default authority.
         * 
         * @param auth
         * @param defaultAuthority
         */
        public LdapAuthenticationToken(Authentication auth, GrantedAuthority defaultAuthority) {
            this.auth = auth;
            if (auth.getAuthorities() != null) {
                this.authorities.addAll(Arrays.asList(auth.getAuthorities()));
            }
            if (defaultAuthority != null) {
                this.authorities.add(defaultAuthority);
            }
            super.setAuthenticated(true);
        }
    
        /**
         * Construct a new LdapAuthenticationToken, using an existing Authentication object and
         * granting all users a default authority.
         * 
         * @param auth
         * @param defaultAuthority
         */
        public LdapAuthenticationToken(Authentication auth, String defaultAuthority) {
            this(auth, new GrantedAuthorityImpl(defaultAuthority));
        }
    
        public GrantedAuthority[] getAuthorities() {
            GrantedAuthority[] authoritiesArray = this.authorities.toArray(new GrantedAuthority[0]);
            return authoritiesArray;
        }
    
        public void addAuthority(GrantedAuthority authority) {
            this.authorities.add(authority);
        }
    
        public Object getCredentials() {
            return auth.getCredentials();
        }
    
        public Object getPrincipal() {
            return auth.getPrincipal();
        }
    
        /**
         * Retrieve the LDAP context attached to this user's authentication object.
         * 
         * @return the LDAP context
         */
        public InitialLdapContext getContext() {
            return context;
        }
    
        /**
         * Attach an LDAP context to this user's authentication object.
         * 
         * @param context
         *          the LDAP context
         */
        public void setContext(InitialLdapContext context) {
            this.context = context;
        }
    
    }
    

    거기에 약간의 비트가 필요하다는 것을 알 수 있습니다.

    예를 들어, 내 앱은 로그인 한 후 사용자가 나중에 사용할 수 있도록 성공적으로 로그인 한 LDAP 컨텍스트를 유지해야했습니다.이 앱의 목적은 사용자가 AD 자격 증명을 통해 로그인 한 다음 추가 AD 관련 기능을 수행하도록하는 것입니다. 그래서 나는 LDAP 컨텍스트를 첨부 할 수있는 (Spring의 기본 인증 토큰이 아닌) 사용자 지정 인증 토큰 인 LdapAuthenticationToken을 가지고있다. LdapAuthenticationProvider.authenticate ()에서 토큰을 만들고 다시 전달합니다. LdapAuthenticatorImpl.authenticate ()에서 사용자의 Spring 인증 객체에 추가 할 수 있도록 로그인 된 컨텍스트를 반환 객체에 첨부합니다.

    또한 LdapAuthenticationProvider.authenticate ()에서 모든 로그인 한 사용자에게 ROLE_USER 역할을 할당합니다. 그러면 내 intercept-url 요소에서 해당 역할을 테스트 할 수 있습니다. 테스트하려는 역할에 관계없이이 역할을 수행하거나 Active Directory 그룹이나 기타 역할을 기반으로 역할을 할당 할 수도 있습니다.

    마지막으로, 그 결과로, LdapAuthenticationProvider.authenticate ()를 구현 한 방식은 유효한 AD 계정을 가진 모든 사용자에게 동일한 ROLE_USER 역할을 제공합니다. 물론이 방법을 사용하면 사용자에 대한 추가 테스트 (예 : 특정 AD 그룹의 사용자)를 수행하고 그런 방식으로 역할을 할당하거나 심지어 사용자 액세스 권한을 부여하기 전에 일부 조건을 테스트 할 수도 있습니다.

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

    2.참고로, Spring Security 3.1은 Active Directory 전용 인증 공급자를 보유하고 있습니다.

    참고로, Spring Security 3.1은 Active Directory 전용 인증 공급자를 보유하고 있습니다.

  3. ==============================

    3.최신 상태로 가져 오면됩니다. Spring Security 3.0은 ldap-bind뿐 아니라 질의 및 비교 인증을위한 기본 구현을 갖춘 완벽한 패키지를 제공합니다.

    최신 상태로 가져 오면됩니다. Spring Security 3.0은 ldap-bind뿐 아니라 질의 및 비교 인증을위한 기본 구현을 갖춘 완벽한 패키지를 제공합니다.

  4. ==============================

    4.스프링 보안 2.0.4를 사용하여 활성 디렉토리에 대해 인증 할 수있었습니다.

    스프링 보안 2.0.4를 사용하여 활성 디렉토리에 대해 인증 할 수있었습니다.

    나는 설정을 문서화했다.

    http://maniezhilan.blogspot.com/2008/10/spring-security-204-with-active.html

  5. ==============================

    5.위의 누가의 대답에서와 같이 :

    위의 누가의 대답에서와 같이 :

    다음은 ActiveDirectoryLdapAuthenticationProvider를 사용하여이 작업을 쉽게 수행 할 수있는 방법에 대한 세부 정보입니다.

    resources.groovy에서 :

    ldapAuthProvider1(ActiveDirectoryLdapAuthenticationProvider,
            "mydomain.com",
            "ldap://mydomain.com/"
    )
    

    Config.groovy에서 :

    grails.plugin.springsecurity.providerNames = ['ldapAuthProvider1']
    

    이것은 필요한 모든 코드입니다. Config.groovy의 다른 grails.plugin.springsecurity.ldap. * 설정은이 AD 설정에 적용되지 않기 때문에 거의 모두 제거 할 수 있습니다.

    설명서는 다음을 참조하십시오. http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

  6. ==============================

    6.SSL을 사용하지 않는 LDAP 인증은 LDAP 서 v로 변환 될 때 사용자 권한 정보를 볼 수있는 사람에게는 안전하지 않습니다. 인증을 위해 LDAPS : \ 프로토콜을 사용하는 것이 좋습니다. 스프링 부분에는 큰 변화가 필요하지 않지만 인증서와 관련된 몇 가지 문제가있을 수 있습니다. 자세한 내용은 SSL을 사용하는 Spring의 LDAP Active Directory 인증을 참조하십시오.

    SSL을 사용하지 않는 LDAP 인증은 LDAP 서 v로 변환 될 때 사용자 권한 정보를 볼 수있는 사람에게는 안전하지 않습니다. 인증을 위해 LDAPS : \ 프로토콜을 사용하는 것이 좋습니다. 스프링 부분에는 큰 변화가 필요하지 않지만 인증서와 관련된 몇 가지 문제가있을 수 있습니다. 자세한 내용은 SSL을 사용하는 Spring의 LDAP Active Directory 인증을 참조하십시오.

  7. ==============================

    7.위의 누가의 대답에서 :

    위의 누가의 대답에서 :

    Spring Security 3.1.1에서 위의 내용을 시도해 보았습니다. ldap과 약간의 변경 사항이 있습니다 - 사용자가 원래의 경우와 같은 멤버가 된 활성 디렉토리 그룹.

    이전에 ldap에서 그룹은 대문자로되어 있고 "ROLE_"이라는 접두어가 붙어있어서 프로젝트에서 텍스트 검색으로 쉽게 찾을 수 있었지만 어떤 이상한 이유로 2 개의 개별 그룹 만이 대소 문자로 구분되는 경우 (예 : 즉 계정 및 계정).

    또한이 구문을 사용하려면 도메인 컨트롤러 이름과 포트를 수동으로 지정해야하므로 중복성이 조금은 두렵습니다. 확실히 Samba 4 하우투에서와 같이 Java에서 도메인에 대한 SRV DNS 레코드를 검색하는 방법이 있습니다.

    $ host -t SRV _ldap._tcp.samdom.example.com.
    _ldap._tcp.samdom.example.com has SRV record 0 100 389 samba.samdom.example.com.
    

    그 다음에 정기적으로 A 검색을 수행합니다.

    $ host -t A samba.samdom.example.com.
    samba.samdom.example.com has address 10.0.0.1
    

    (실제로 _kerberos SRV 레코드도 조회해야 할 수도 있습니다 ...)

    위의 내용은 Samba4.0rc1에서였으며, Samba 3.x LDAP 환경에서 Samba AD 환경으로 점진적으로 업그레이드하고 있습니다.

  8. ==============================

    8.

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);
    
    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                  .antMatchers("/").permitAll()
                  .anyRequest().authenticated();
                .and()
                  .formLogin()
                .and()
                  .logout();
    }
    
    @Bean
    public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
        ActiveDirectoryLdapAuthenticationProvider authenticationProvider = 
            new ActiveDirectoryLdapAuthenticationProvider("<domain>", "<url>");
    
        authenticationProvider.setConvertSubErrorCodesToExceptions(true);
        authenticationProvider.setUseAuthenticationRequestCredentials(true);
    
        return authenticationProvider;
    }
    }
    
  9. from https://stackoverflow.com/questions/84680/how-do-you-authenticate-against-an-active-directory-server-using-spring-security by cc-by-sa and MIT license