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

StackOverflow https://stackoverflow.com/questions/84680

문제

나는 사용자가 로그인해야 하는 Spring 웹 애플리케이션을 작성하고 있습니다.우리 회사에는 이 목적으로 활용하고 싶은 Active Directory 서버가 있습니다.그러나 Spring Security를 ​​사용하여 서버에 연결하는 데 문제가 있습니다.

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

LDAP URL을 잘못된 IP 주소로 변경해도 예외가 발생하지 않으며 아무 것도 발생하지 않는지 궁금합니다. 견딜 수 없는 시작하려면 서버에 연결하세요.

웹 애플리케이션은 정상적으로 시작되지만 로그인 페이지에 입력하는 모든 정보는 거부됩니다.나는 이전에 InMemoryDaoImpl을 사용했는데 잘 작동했기 때문에 내 애플리케이션의 나머지 부분은 올바르게 구성된 것 같습니다.

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

  <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>
도움이 되었습니까?

해결책

나도 당신과 똑같은 벽에 부딪힌 경험이 있었고 결국 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;
    }

}

그런 다음 LdapAuthenticatorImpl 클래스는 다음과 같습니다.

/**
 * 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 그룹에 속해 있습니까?) 그런 식으로 역할을 할당하거나 사용자에게 액세스 권한을 부여하기 전에 일부 조건을 테스트할 수도 있습니다. 모두.

다른 팁

참고로 Spring Security 3.1에는 인증 공급자가 있습니다. 특히 Active Directory용.

이것을 최신 상태로 만들기 위해서입니다.스프링 시큐리티 3.0에는 완전한 패키지 ldap-bind 및 쿼리 및 비교 인증 전용 기본 구현을 사용합니다.

Spring Security 2.0.4를 사용하여 Active Directory에 대해 인증할 수 있었습니다.

설정을 문서화했습니다.

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

위의 Luke의 답변에서와 같이 :

Spring Security 3.1에는 Active Directory 전용 인증 공급자가 있습니다.

ActiveDirectoryLdapAuthenticationProvider를 사용하여 이 작업을 쉽게 수행하는 방법에 대한 자세한 내용은 다음과 같습니다.

resources.groovy에서:

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

Config.groovy에서:

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

이것이 필요한 코드의 전부입니다.이 AD 설정에는 적용되지 않으므로 Config.groovy에서 다른 모든 grails.plugin.springsecurity.ldap.* 설정을 제거할 수 있습니다.

문서는 다음을 참조하세요.http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

SSL이 없는 LDAP 인증은 사용자 자격 증명이 LDAP 서버로 전송될 때 누구나 볼 수 있어 안전하지 않습니다.인증을 위해 LDAPS:\ 프로토콜을 사용하는 것이 좋습니다.스프링 부분에서는 큰 변경이 필요하지 않지만 인증서와 관련된 몇 가지 문제가 발생할 수 있습니다.보다 SSL을 사용하는 Spring의 LDAP Active Directory 인증 상세 사항은

위의 Luke의 답변에서 :

참고로 Spring Security 3.1에는 인증 제공 업체가 있습니다 [특히 Active Directory의 경우] [1].

[1]: http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

Spring Security 3.1.1에서 위의 작업을 시도했습니다.ldap에는 약간의 변경 사항이 있습니다. 사용자가 구성원으로 속한 활성 디렉터리 그룹이 원래 사례로 전달됩니다.

이전에는 ldap에서 그룹에 대문자를 사용하고 "ROLE_" 접두사가 붙었습니다. 이로 인해 프로젝트에서 텍스트 검색으로 쉽게 찾을 수 있었지만 이상한 이유로 대/소문자로만 구별되는 2개의 별도 그룹이 있는 경우 유닉스 그룹에서 분명히 문제가 발생할 수 있습니다. 즉, 계정 및 계정).

또한 구문에는 도메인 컨트롤러 이름과 포트를 수동으로 지정해야 하므로 중복성이 다소 두렵습니다.확실히 Java에서 도메인에 대한 SRV DNS 레코드를 찾는 방법이 있습니다. 즉, (Samba 4 하우투에서) 다음과 같습니다.

$ 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 환경으로 점진적으로 업그레이드하고 있습니다.

스프링을 사용하는 경우 보안 4 주어진 클래스를 사용하여 동일하게 구현할 수도 있습니다

  • SecurityConfig.java
@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;
}
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top