使用 Spring LDAP 进行用户认证

本节介绍使用 Spring LDAP 进行用户认证。它包含以下主题:

基本认证

虽然 ContextSource 的核心功能是提供供 LdapClientLdapTemplate 使用的 DirContext 实例,但您也可以使用它来针对 LDAP 服务器进行用户认证。ContextSourcegetContext(principal, credentials) 方法正是实现了此功能。它根据 ContextSource 的配置构建一个 DirContext 实例,并使用提供的 principal 和 credentials 进行认证。自定义的 authenticate 方法示例如下:

public boolean authenticate(String userDn, String credentials) {
  DirContext ctx = null;
  try {
    ctx = contextSource.getContext(userDn, credentials);
    return true;
  } catch (Exception e) {
    // Context creation failed - authentication did not succeed
    logger.error("Login failed", e);
    return false;
  } finally {
    // It is imperative that the created DirContext instance is always closed
    LdapUtils.closeContext(ctx);
  }
}

提供给 authenticate 方法的 userDn 必须是要认证用户的完整 DN(无论 ContextSource 上的 base 设置为何)。通常您需要基于(例如)用户名执行 LDAP 搜索来获取此 DN。以下示例展示了如何实现:

private String getDnForUser(String uid) {
  List<String> result = ldapClient.search()
      .query(query().where("uid").is(uid))
      .toList((Object ctx) -> ((DirContextOperations) ctx).getNameInNamespace());

  if(result.size() != 1) {
    throw new RuntimeException("User not found or not unique");
  }

  return result.get(0);
}

此方法有一些缺点。您被迫需要关心用户的 DN,只能搜索用户的 uid,并且搜索总是从树的根(空路径)开始。更灵活的方法允许您指定搜索基准、搜索过滤器和凭据。Spring LDAP 在 LdapClient 中包含了一个提供此功能的认证方法。

当您使用此方法时,认证变得非常简单,如下所示:

示例 1. 使用 Spring LDAP 认证用户
ldapClient.authenticate().query(query().where("uid").is("john.doe")).password("secret").execute();
在已认证的 Context 上执行操作所述,某些配置可能要求您执行额外的操作才能实际进行认证。有关详细信息,请参阅在已认证的 Context 上执行操作
不要编写自己的自定义 authenticate 方法。请使用 Spring LDAP 中提供的方法。

在已认证的 Context 上执行操作

某些认证方案和 LDAP 服务器要求在创建的 DirContext 实例上执行某些操作才能实际进行认证。您应该测试并确保您的服务器配置和认证方案的行为方式。否则,可能会导致用户无论提供的 DN 和凭据是否正确都能进入您的系统。以下示例展示了一个简单的 authenticate 方法的实现,其中在已认证的 context 上执行了硬编码的 lookup 操作

public boolean myAuthenticate(String userDn, String credentials) {
  DirContext ctx = null;
  try {
    ctx = contextSource.getContext(userDn, credentials);
    // Take care here - if a base was specified on the ContextSource
    // that needs to be removed from the user DN for the lookup to succeed.
    ctx.lookup(userDn);
    return true;
  } catch (Exception e) {
    // Context creation failed - authentication did not succeed
    logger.error("Login failed", e);
    return false;
  } finally {
    // It is imperative that the created DirContext instance is always closed
    LdapUtils.closeContext(ctx);
  }
}

如果操作可以作为回调接口的实现提供,而不是将操作限制为始终是 lookup,那会更好。Spring LDAP 包含了 AuthenticatedLdapEntryContextMapper 回调接口以及相应的 authenticate 方法。

此方法允许在已认证的 context 上执行任何操作,如下所示:

示例 2. 使用 Spring LDAP 在已认证的 context 上执行 LDAP 操作
AuthenticatedLdapEntryContextMapper<DirContextOperations> mapper = new AuthenticatedLdapEntryContextMapper<DirContextOperations>() {
  public DirContextOperations mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
    try {
      return (DirContextOperations) ctx.lookup(ldapEntryIdentification.getRelativeName());
    }
    catch (NamingException e) {
      throw new RuntimeException("Failed to lookup " + ldapEntryIdentification.getRelativeName(), e);
    }
  }
};

ldapClient.authenticate().query(query().where("uid").is("john.doe")).password("secret").execute(mapper);

已废弃的认证方法

除了前面章节描述的 authenticate 方法外,您还可以使用一些已废弃的方法进行认证。虽然这些方法也能正常工作,但我们建议改用 LdapQuery 方法。

使用 Spring Security

虽然前面章节描述的方法对于简单的认证场景可能已足够,但这方面的需求通常会快速扩展。涉及多个方面,包括认证、授权、Web 集成、用户上下文管理等。如果您认为需求可能超出简单的认证范围,那么出于安全目的,您绝对应该考虑使用Spring Security。它是一个功能齐全、成熟的安全框架,解决了上述方面以及其他几个方面的问题。