2008-09-17 31 views
27

Tôi đang viết một ứng dụng web Spring yêu cầu người dùng đăng nhập. Công ty của tôi có một máy chủ Active Directory mà tôi muốn sử dụng cho mục đích này. Tuy nhiên, tôi đang gặp sự cố khi sử dụng Spring Security để kết nối với máy chủ.Làm thế nào để bạn xác thực đối với một máy chủ Active Directory bằng cách sử dụng Spring Security?

Tôi đang sử dụng Spring 2.5.5 và 2.0.3 Xuân An, cùng với Java 1.6.

Nếu tôi thay đổi URL LDAP thành địa chỉ IP sai, nó không ném ngoại lệ hoặc bất cứ điều gì, vì vậy tôi tự hỏi nếu nó ngay cả cố gắng để kết nối với máy chủ để bắt đầu.

Mặc dù ứng dụng web khởi động tốt, bất kỳ thông tin tôi nhập vào trang đăng nhập bị từ chối. Trước đây tôi đã sử dụng một InMemoryDaoImpl, hoạt động tốt, vì vậy phần còn lại của ứng dụng của tôi dường như được cấu hình đúng.

Dưới đây là đậu an ninh liên quan đến tôi:

<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

Đây không thực sự là câu trả lời quá nhiều như một câu hỏi làm rõ - bạn có đăng nhập đã quay tất cả các con đường cho gói bảo mật mùa xuân chưa? –

+0

Tôi đã bật tính năng ghi nhật ký cho mọi thứ. Không thấy bất kỳ tin nhắn nào được đăng nhập ... Tôi đã cập nhật câu hỏi của mình với cấu hình Log4J của mình. – Michael

+0

Hãy cho tôi biết các tệp jar cần thiết cho việc này. xin vui lòng – addy

Trả lời

35

tôi đã có kinh nghiệm cùng đập-my-đầu-chống-the-tường bạn đã làm, và kết thúc bằng văn bản một nhà cung cấp chứng thực tùy chỉnh mà không một LDAP truy vấn đối với máy chủ Active Directory.

Vì vậy, đậu an ninh liên quan đến tôi là:

<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> 

Sau đó, lớp 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; 
    } 

} 

Sau đó, lớp 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 = ""; 
     } 
    } 

} 

Và cuối cùng, lớp 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; 
    } 

} 

Bạn sẽ nhận thấy rằng có một vài bit trong đó mà bạn có thể không cần. Ví dụ: ứng dụng của tôi cần giữ lại ngữ cảnh LDAP đã đăng nhập thành công để người dùng sử dụng sau khi đã đăng nhập - mục đích của ứng dụng là cho phép người dùng đăng nhập thông qua thông tin đăng nhập AD của họ và sau đó thực hiện thêm AD- các hàm liên quan. Vì vậy, vì lý do đó, tôi có một mã thông báo xác thực tùy chỉnh, LdapAuthenticationToken, mà tôi chuyển qua (thay vì mã thông báo Xác thực mặc định của Spring) cho phép tôi đính kèm ngữ cảnh LDAP. Trong LdapAuthenticationProvider.authenticate(), tôi tạo mã thông báo đó và chuyển nó trở lại; trong LdapAuthenticatorImpl.authenticate(), tôi đính kèm bối cảnh đã đăng nhập vào đối tượng trả về để nó có thể được thêm vào đối tượng xác thực mùa xuân của người dùng.

Ngoài ra, trong LdapAuthenticationProvider.authenticate(), tôi gán tất cả người dùng đã đăng nhập vai trò ROLE_USER - đó là điều cho phép tôi kiểm tra vai trò đó trong các phần tử chặn url của tôi. Bạn sẽ muốn làm cho phù hợp với bất kỳ vai trò nào bạn muốn thử nghiệm, hoặc thậm chí gán vai trò dựa trên các nhóm Active Directory hoặc bất cứ điều gì.

Cuối cùng, và một hệ quả cho điều đó, cách tôi triển khai LdapAuthenticationProvider.authenticate() cung cấp cho tất cả người dùng các tài khoản AD hợp lệ có cùng vai trò ROLE_USER. Rõ ràng, trong phương thức đó, bạn có thể thực hiện các kiểm tra khác trên người dùng (nghĩa là người dùng trong một nhóm AD cụ thể?) Và chỉ định vai trò theo cách đó hoặc thậm chí kiểm tra một số điều kiện trước khi cấp quyền truy cập cho người dùng tại tất cả.

+0

Cho tôi biết các tập tin jar cần thiết cho việc này. xin vui lòng. – addy

+0

Tôi phải đối mặt với cùng một vấn đề. Nhưng khi nhiều phương pháp đó không còn được sử dụng trong các phiên bản Spring mới, tôi không thể thực hiện công việc tương tự như thế này nữa. Tôi đã hỏi câu hỏi của mình tại đây http://stackoverflow.com/questions/32070142/spring-security-configuratiion-to-authenticate-ldap-user. Tôi sẽ tự hỏi nếu bạn có thể giúp tôi trong @delfuego này – moha

2

Chỉ cần mang cái này đến một tình trạng up-to-date. Spring Security 3.0 có complete package với các triển khai mặc định dành cho liên kết ldap cũng như truy vấn và so sánh xác thực.

0

Xác thực LDAP không có SSL không an toàn, bất kỳ ai cũng có thể xem thông tin xác thực người dùng khi được chuyển sang máy chủ LDAP. Tôi đề nghị sử dụng LDAPS: \ protocol để xác thực. Nó không yêu cầu bất kỳ sự thay đổi lớn nào về phần mùa xuân nhưng bạn có thể chạy với một số vấn đề liên quan đến chứng chỉ. Xem LDAP Active Directory authentication in Spring with SSL để biết thêm chi tiết

16

Để tham khảo, Bảo mật mùa xuân 3.1 có nhà cung cấp xác thực specifically for Active Directory.

+1

+1 Điều này sẽ là câu trả lời hàng đầu. Từ kinh nghiệm tôi có thể nói rằng bạn đang làm cho cuộc sống rất khó khăn cho chính mình nếu bạn sử dụng LdapAuthenticationProvider để xác thực chống lại AD. Trong các phiên bản Spring hiện đại, bạn có thể làm những gì bạn muốn trong ít hơn 5 dòng mã. Xem liên kết Luke đã cung cấp ở trên. Tôi cũng đã đưa ra chi tiết trong câu trả lời của tôi dưới đây. – Cookalino

0

Từ câu trả lời của Luke trên:

Để tham khảo, Xuân An 3.1 có một nhà cung cấp chứng thực [đặc biệt cho Active Directory] [1].

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

tôi đã cố gắng nêu trên với Xuân An 3.1.1: có một số thay đổi nhỏ từ ldap - các nhóm hoạt động thư mục người dùng là thành viên của đi qua như trường hợp ban đầu .

Trước đây dưới ldap, các nhóm được viết hoa và bắt đầu bằng "ROLE_", giúp chúng dễ dàng tìm thấy bằng tìm kiếm văn bản trong dự án, nhưng rõ ràng có thể gặp sự cố trong nhóm unix nếu vì lý do lạ nào chỉ có 2 nhóm riêng biệt phân biệt theo từng trường hợp (ví dụ: tài khoản và Tài khoản).

Ngoài ra cú pháp yêu cầu đặc điểm kỹ thuật thủ công của tên và bộ điều khiển tên miền, điều này làm cho nó hơi đáng sợ đối với dự phòng. Chắc chắn có một cách nhìn lên bản ghi SRV DNS cho tên miền trong java, tức là tương đương với (từ Samba 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. 

tiếp theo thường xuyên Một tra cứu:

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

(Trên thực tế có thể cần tìm kiếm bản ghi SRK của _kerberos quá ...)

Ở trên là với Samba4.0rc1, chúng tôi đang dần nâng cấp từ môi trường Samba 3.x LDAP lên Samba AD one.

0

Như trong câu trả lời của Luke trên:

Xuân An 3.1 có một nhà cung cấp xác thực đặc biệt cho Active Directory.

Đây là chi tiết về cách thức này có thể dễ dàng thực hiện bằng cách sử dụng ActiveDirectoryLdapAuthenticationProvider.

Trong tài nguyên.groovy:

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

Trong Config.groovy:

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

Đây là tất cả các mã mà bạn cần. Bạn có thể loại bỏ tất cả các cài đặt grails.plugin.springsecurity.ldap. * Khác trong Config.groovy vì chúng không áp dụng cho thiết lập AD này.

Đối với tài liệu, xem: http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

0

Nếu bạn đang sử dụng Spring an ninh 4 bạn cũng có thể thực hiện tương tự sử dụng lớp trao

  • 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; 
} 
} 
Các vấn đề liên quan