2008-09-17 152 views
27

我在寫一個需要用戶登錄的Spring Web應用程序。我的公司有一個Active Directory服務器,我想利用這個目的。但是,我無法使用Spring Security連接到服務器。如何使用Spring Security對Active Directory服務器進行身份驗證?

我使用Spring 2.5.5和Spring Security 2.0.3以及Java 1.6。

如果我將LDAP URL更改爲錯誤的IP地址,它不會拋出異常或任何內容,所以我想知道是否嘗試連接到服務器開始。

儘管Web應用程序啓動得很好,但我輸入到登錄頁面的任何信息都被拒絕。我以前使用過的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> 
+0

這不是一個真正的答案,而是一個澄清的問題 - 您是否已經將日誌記錄轉換爲Spring安全包的所有內容? – 2008-09-17 17:52:40

+0

我有日誌記錄開啓了一切。沒有看到任何正在記錄的消息......我用我的Log4J配置更新了我的問題。 – Michael 2008-09-17 18:35:26

+0

告訴我這個所需的jar文件。請 – addy 2013-09-06 10:28:14

回答

35

我擁有了你做同樣的敲打,我的頭抵牆式體驗,並最終寫入,做一個LDAP自定義身份驗證提供者針對Active Directory服務器進行查詢。

所以我的安全相關的豆類:

<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-相關功能。因此,我有一個自定義身份驗證令牌LdapAuthenticationToken,它傳遞給我(附加LDAP上下文,而不是Spring的默認身份驗證令牌)。在LdapAuthenticationProvider.authenticate()中,我創建了該令牌並將其傳回;在LdapAuthenticatorImpl.authenticate()中,我將登錄的上下文附加到返回對象,以便它可以添加到用戶的Spring認證對象中。

此外,在LdapAuthenticationProvider.authenticate()中,我爲所有登錄用戶分配了ROLE_USER角色 - 這就是讓我在我的攔截url元素中測試該角色的原因。無論您想要測試哪種角色,您都希望進行匹配,甚至可以根據Active Directory組或其他角色分配角色。

最後,由此推論,我實現LdapAuthenticationProvider.authenticate()的方式爲所有用戶提供了有效的AD帳戶,它們具有相同的ROLE_USER角色。顯然,在該方法中,您可以對用戶(即特定AD組中的用戶?)執行進一步測試,並且甚至在准許用戶訪問全部之前分配角色,甚至測試某些條件。

2

只是把這個給了最新的狀態。 Spring Security 3.0有一個complete package,默認實現專用於ldap-bind以及查詢和比較認證。

0

沒有SSL的LDAP認證是不安全的任何人都可以在用戶憑證被轉移到LDAP服務器時看到用戶憑證。我建議使用LDAPS:\協議進行身份驗證。它不需要對彈簧部分進行任何重大更改,但您可能會遇到一些與證書相關的問題。有關更多詳細信息,請參見LDAP Active Directory authentication in Spring with SSL

0

盧克的回答以上:

僅供參考使用,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

我試圖上述與春季安全3.1.1:有從LDAP一些輕微的變化 - 活性目錄組用戶是作爲原始情況來通過的成員。

以前,在ldap下,這些組被大寫,並帶有「ROLE_」前綴,這使得他們很容易在項目中使用文本搜索來查找,但是如果出於某種奇怪的原因只有2個獨立的組,按情況區分(即賬戶和賬戶)。

此外,語法還需要手動指定域控制器名稱和端口,這使冗餘有點可怕。當然有一種仰視的SRV DNS記錄在Java領域中,即相當於一種方式(從桑巴4 HOWTO):

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

其次是定期的查找:

$ 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之一。

0

作爲盧克的回答以上:

的Spring Security 3.1對Active Directory中的身份驗證專門供應商。

下面是使用ActiveDirectoryLdapAuthenticationProvider可以輕鬆完成的細節。

在資源。常規:

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

0

如果您在使用Spring 安全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; 
} 
} 
相關問題