簡體   English   中英

Spring Security LDAP 認證用戶名和密碼來自登錄表單

[英]Spring Security Ldap authentication userDn and password from login form

我正在嘗試使用WebSecurityConfigurerAdapter實現 Spring Security LDAP 身份驗證。

到目前為止它工作正常,但在我的情況下的問題是我不希望上下文的用戶名和密碼被硬編碼。 它必須是用戶的登錄名和密碼,所以我的問題是如何從登錄表單構建用戶名和密碼的上下文和設置?

這是我正在使用的代碼:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().fullyAuthenticated()
                .and()
            .formLogin();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .ldapAuthentication()
                .userSearchFilter("(sAMAccountName={0})")
                .contextSource(contextSource());
    }

    @Bean
    public BaseLdapPathContextSource contextSource() {
        LdapContextSource bean = new LdapContextSource();
        bean.setUrl("ldap://10.10.10.10:389");
        bean.setBase("DC=myDomaine,DC=com");
        //instead of this i want to put here the username and password provided by the user
        bean.setUserDn("myDomaine\\username");
        bean.setPassword("password");
        bean.setPooled(true);
        bean.setReferral("follow");
        bean.afterPropertiesSet();
        return bean;
    }
}

謝謝!

您的代碼應該可以正常工作。 硬編碼的用戶名和密碼僅用於創建與 ldap 服務器的綁定。 登錄表單中提供的用戶名和密碼僅使用您的代碼進行身份驗證。

我使用以下代碼來執行 ldap 身份驗證。

public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter("sAMAccountName={0}").contextSource().url(this.ldapUrl).managerDn(this.managerDn)
    .managerPassword(this.managerPassword);
}

其中 manager 是用於創建與服務器綁定的 ldap 帳戶。

contextSource 中的 userDN 和 password 參數是必需參數。 就像管理員用戶名和密碼一樣,您可以獲取或創建到 ldap 服務器的初始連接。

讓您能夠從登錄表單驗證用戶名和密碼。 您可以使用 ldapTemplate:

        @Bean
    public BaseLdapPathContextSource contextSource() {
        LdapContextSource bean = new LdapContextSource();
        bean.setUrl("ldap://10.10.10.10:389");
        bean.setBase("DC=myDomaine,DC=com");
        //instead of this i want to put here the username and password provided by the user
        bean.setUserDn("myDomaine\\username");
        bean.setPassword("password");
        bean.setPooled(true);
        bean.setReferral("follow");
        bean.afterPropertiesSet();
        return bean;
    }



  @Bean
  public LdapTemplate ldapTemplate() {
      LdapTemplate template = new LdapTemplate(contextSource());

      return template;
  }

然后在您的服務類實現中使用它:

@Service
public class LdapUserServiceImpl implements LdapUserService, BaseLdapNameAware {
    @Autowired
    protected ContextSource contextSource;
    @Autowired
    protected LdapTemplate ldapTemplate;

    @Override
    public boolean authenticate(String userDn, String credentials) {
        AndFilter filter = new AndFilter();
        filter.and(new EqualsFilter("sAMAccountName", userDn));

        return ldapTemplate.authenticate("", filter.toString(), credentials);
    }

}

然后調用此服務,從登錄表單傳遞用戶名和密碼,如下所示:

boolean isAuthenticated = ldapUserService.authenticate(loginForm.getUsername(), loginForm.getPassword());

您可以使用自定義的身份驗證提供程序進行身份驗證。

在您的安全配置中,您可以自動連接 CustomAuthenticationProvider,它可以從登錄表單中獲取用戶名和密碼:


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomAuthenticationProvider customAuthProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth)  {
        auth.authenticationProvider(customAuthProvider);
    }
}

現在只需實現 CustomAuthenticationProvider

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private static final Logger LOG = LoggerFactory.getLogger(CustomAuthenticationProvider.class);

    @Value("${ldap.host}")
    String ldapHost;

    @Value("${ldap.port}")
    String ldapPort;

    @Value("${ldap.base-dn}")
    String baseDomainName;

    @Value("${ldap.domain-prefix}")
    String domainPrefix;

    @Value("${ldap.read.timeout}")
    String timeout;

    private static final String DEFAULT_JNDI_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";


    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {

        //user and password are from user's input from login form...
        String user= authentication.getName();
        String password = authentication.getCredentials().toString();

        try {
            Set<String> roles = authenticate(user, password);

           if (CollectionUtils.isEmpty(roles))
                return null;

            List<GrantedAuthority> authorityList = new ArrayList<>();

            for(String role: roles){
                authorityList.add(new SimpleGrantedAuthority(role));
            }

            return new UsernamePasswordAuthenticationToken(user, password, authorityList);

        } catch (NamingException ex) {
            LOG.info("Naming Exception",ex);

        }
        return null;
    }

    public Set<String> authenticate(final String username, final String password) throws NamingException {

        InitialLdapContext ctx = null;
        NamingEnumeration<SearchResult> results = null;

        try {
            final Hashtable<String, String> ldapEnvironment = new Hashtable<>();
            ldapEnvironment.put(Context.INITIAL_CONTEXT_FACTORY, DEFAULT_JNDI_CONTEXT_FACTORY);
            ldapEnvironment.put(Context.PROVIDER_URL, "ldap://" + ldapHost + ":" + ldapPort + "/" + baseDomainName);
            ldapEnvironment.put(Context.SECURITY_PROTOCOL, "ssl");
            ldapEnvironment.put(Context.SECURITY_CREDENTIALS, password);
            ldapEnvironment.put(Context.SECURITY_PRINCIPAL, domainPrefix + '\\' + username);

            ldapEnvironment.put("com.sun.jndi.ldap.read.timeout", timeout);
            ldapEnvironment.put("com.sun.jndi.ldap.connect.timeout", timeout);

            ctx = new InitialLdapContext(ldapEnvironment, null);

            final SearchControls constraints = new SearchControls();
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
            constraints.setReturningAttributes(new String[]{"memberOf"});
            constraints.setReturningObjFlag(true);
            results = ctx.search("", "(sAMAccountName=" + username + ")", constraints);

            if (!results.hasMore()) {
                LOG.warn(".authenticate(" + ldapHost + "," + username + "): unable to locate " + username);
                return null;
            }

            final Set<String> adGroups = new TreeSet<>();
            final SearchResult entry = results.next();
            for (NamingEnumeration valEnum = entry.getAttributes().get("memberOf").getAll(); valEnum.hasMoreElements(); ) {
                String dn = (String) valEnum.nextElement();
                int i = dn.indexOf(",");
                if (i != -1) {
                    dn = dn.substring(0, i);
                }

                if (dn.startsWith("CN=")) {
                    dn = dn.substring("CN=".length());
                }
                adGroups.add(dn);
            }
            return adGroups;
        }
        finally {
            try {
                if (null != results)
                    results.close();
            }
            catch (Throwable ignored) {
            }
            try {
                if (null != ctx)
                    ctx.close();
            }
            catch (Throwable ignored) {
            }
        }
    }


    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(
                UsernamePasswordAuthenticationToken.class);
    }
}

通過不重寫自己的 LdapAuthenticationProvider 來節省自己的時間,現有的 ActiveDirectoryLdapAuthenticationProvider 將使用收到的憑據進行 LDAP 身份驗證,如果您想做更多,您還可以添加 searchFilter(例如,查看用戶是否也屬於特定組)

相關文檔:

https://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.html

示例片段:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private LdapProperties ldapProperties;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
    }

    @Bean
    public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
        ActiveDirectoryLdapAuthenticationProvider authenticationProvider =
                new ActiveDirectoryLdapAuthenticationProvider(ldapProperties.getDomain(), ldapProperties.getProviderUrl());

        authenticationProvider.setConvertSubErrorCodesToExceptions(true);
        authenticationProvider.setUseAuthenticationRequestCredentials(true);
        //if you're not happy on the default searchFilter, you can set your own. See https://docs.spring.io/spring-security/site/docs/4.2.18.RELEASE/apidocs/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.html#setSearchFilter-java.lang.String-
        authenticationProvider.setSearchFilter("(&(objectClass=user)(cn={1}))");
        return authenticationProvider;
    }
...
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM