LDAP 認証

LDAP(Lightweight Directory Access Protocol)は、ユーザー情報の主要リポジトリとして、および認証サービスとして、組織でよく使用されます。また、アプリケーションユーザーのロール情報を保存するためにも使用できます。

Spring Security の LDAP ベースの認証は、認証のためにユーザー名 / パスワードを受け入れるように構成されている場合、Spring Security によって使用されます。ただし、認証にユーザー名とパスワードを使用するにもかかわらず、UserDetailsService を使用しません。これは、bind authentication で LDAP サーバーがパスワードを返さないため、アプリケーションがパスワードの検証を実行できないためです。

LDAP サーバーの構成方法にはさまざまなシナリオがあるため、Spring Security の LDAP プロバイダーは完全に構成可能です。認証とロール取得に別々の戦略インターフェースを使用し、さまざまな状況を処理するように構成できるデフォルトの実装を提供します。

必要な依存関係

まず、プロジェクトに spring-security-ldap 依存関係を追加します。Spring Boot を使用する場合は、次の依存関係を追加します。

Spring Security LDAP 依存関係
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
</dependency>
depenendencies {
    implementation "org.springframework.boot:spring-boot-starter-data-ldap"
    implementation "org.springframework.security:spring-security-ldap"
}

前提条件

LDAP を Spring Security で使用する前に、LDAP に精通している必要があります。次のリンクは、関連する概念の優れた導入と、フリーの LDAP サーバーである OpenLDAP を使用してディレクトリを設定するためのガイドを提供します: www.zytrax.com/books/ldap/ (英語) 。Java から LDAP にアクセスするために使用される JNDIAPI にある程度精通していることも役立ちます。LDAP プロバイダーではサードパーティの LDAP ライブラリ(Mozilla、JLDAP など)を使用していませんが、Spring LDAP が広く使用されているため、独自のカスタマイズを追加する場合は、そのプロジェクトにある程度精通していると役立つ場合があります。

LDAP 認証を使用する場合は、LDAP 接続プールを適切に構成していることを確認する必要があります。未知の場合は、Java LDAP ドキュメント [Oracle] (英語) を参照してください。

組み込み LDAP サーバーのセットアップ

最初に行う必要があるのは、構成を指す LDAP サーバーがあることを確認することです。簡単にするために、多くの場合、組み込み LDAP サーバーから始めるのが最善です。Spring Security は、次のいずれかの使用をサポートします。

次のサンプルでは、users.ldif をクラスパスリソースとして公開し、2 人のユーザー(user と admin)で組み込み LDAP サーバーを初期化します。どちらのユーザーも password のパスワードを持っています。

users.ldif
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: password

dn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: password

dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
member: uid=admin,ou=people,dc=springframework,dc=org
member: uid=user,ou=people,dc=springframework,dc=org

dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
member: uid=admin,ou=people,dc=springframework,dc=org

組み込み UnboundID サーバー

UnboundID (英語) を使用する場合は、次の依存関係を指定します。

UnboundID の依存関係
  • Maven

  • Gradle

<dependency>
	<groupId>com.unboundid</groupId>
	<artifactId>unboundid-ldapsdk</artifactId>
	<version>6.0.11</version>
	<scope>runtime</scope>
</dependency>
depenendencies {
	runtimeOnly "com.unboundid:unboundid-ldapsdk:6.0.11"
}

次に、EmbeddedLdapServerContextSourceFactoryBean を使用して組み込み LDAP サーバーを構成できます。これにより、Spring Security はインメモリ LDAP サーバーを起動するように指示されます。

組み込み LDAP サーバーの構成
  • Java

  • Kotlin

@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
	return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
}
@Bean
fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
    return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
}

または、組み込み LDAP サーバーを手動で構成することもできます。このアプローチを選択した場合は、組み込み LDAP サーバーのライフサイクルを管理する責任があります。

明示的な組み込み LDAP サーバー構成
  • Java

  • XML

  • Kotlin

@Bean
UnboundIdContainer ldapContainer() {
	return new UnboundIdContainer("dc=springframework,dc=org",
				"classpath:users.ldif");
}
<b:bean class="org.springframework.security.ldap.server.UnboundIdContainer"
	c:defaultPartitionSuffix="dc=springframework,dc=org"
	c:ldif="classpath:users.ldif"/>
@Bean
fun ldapContainer(): UnboundIdContainer {
    return UnboundIdContainer("dc=springframework,dc=org","classpath:users.ldif")
}

組み込み ApacheDS サーバー

Spring Security は ApacheDS 1.x を使用しますが、これは現在は保守されていません。残念ながら、ApacheDS 2.x はマイルストーンバージョンのみをリリースしており、安定したリリースはありません。ApacheDS 2.x の安定リリースが利用可能になったら、更新を検討します。

Apache DS (英語) を使用する場合は、次の依存関係を指定します。

ApacheDS の依存関係
  • Maven

  • Gradle

<dependency>
	<groupId>org.apache.directory.server</groupId>
	<artifactId>apacheds-core</artifactId>
	<version>1.5.5</version>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>org.apache.directory.server</groupId>
	<artifactId>apacheds-server-jndi</artifactId>
	<version>1.5.5</version>
	<scope>runtime</scope>
</dependency>
depenendencies {
	runtimeOnly "org.apache.directory.server:apacheds-core:1.5.5"
	runtimeOnly "org.apache.directory.server:apacheds-server-jndi:1.5.5"
}

次に、組み込み LDAP サーバーを構成できます。

組み込み LDAP サーバーの構成
  • Java

  • XML

  • Kotlin

@Bean
ApacheDSContainer ldapContainer() {
	return new ApacheDSContainer("dc=springframework,dc=org",
				"classpath:users.ldif");
}
<b:bean class="org.springframework.security.ldap.server.ApacheDSContainer"
	c:defaultPartitionSuffix="dc=springframework,dc=org"
	c:ldif="classpath:users.ldif"/>
@Bean
fun ldapContainer(): ApacheDSContainer {
    return ApacheDSContainer("dc=springframework,dc=org", "classpath:users.ldif")
}

LDAP ContextSource

構成を指す LDAP サーバーを作成したら、ユーザーの認証に使用する必要がある LDAP サーバーを指すように Spring Security を構成する必要があります。これを行うには、LDAP ContextSource (JDBC DataSource と同等)を作成します。EmbeddedLdapServerContextSourceFactoryBean をすでに構成している場合、Spring Security は、組み込み LDAP サーバーを指す LDAP ContextSource を作成します。

LDAP サーバーが組み込まれた LDAP コンテキストソース
  • Java

  • Kotlin

@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
	EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean =
			EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
	contextSourceFactoryBean.setPort(0);
	return contextSourceFactoryBean;
}
@Bean
fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
    val contextSourceFactoryBean = EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
    contextSourceFactoryBean.setPort(0)
    return contextSourceFactoryBean
}

または、提供された LDAP サーバーに接続するように LDAP ContextSource を明示的に構成することもできます。

LDAP コンテキストソース
  • Java

  • XML

  • Kotlin

ContextSource contextSource(UnboundIdContainer container) {
	return new DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org");
}
<ldap-server
	url="ldap://localhost:53389/dc=springframework,dc=org" />
fun contextSource(container: UnboundIdContainer): ContextSource {
    return DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org")
}

認証

Spring Security の LDAP サポートは UserDetailsService を使用しません。これは、LDAP バインド認証では、クライアントがパスワードまたはパスワードのハッシュバージョンを読み取れないためです。これは、パスワードを読み取って Spring Security で認証する方法がないことを意味します。

このため、LDAP サポートは LdapAuthenticator インターフェースを介して実装されます。LdapAuthenticator インターフェースは、必要なユーザー属性を取得するロールも果たします。これは、属性のアクセス許可が、使用されている認証の型によって異なる場合があるためです。例: ユーザーとしてバインドする場合は、ユーザー自身の権限で属性を読み取る必要がある場合があります。

Spring Security は、2 つの LdapAuthenticator 実装を提供します。

バインド認証の使用

バインド認証 (英語) は、LDAP を使用してユーザーを認証するための最も一般的なメカニズムです。バインド認証では、ユーザーの資格情報(ユーザー名とパスワード)が LDAP サーバーに送信され、LDAP サーバーがユーザーを認証します。バインド認証を使用する利点は、ユーザーのシークレット(パスワード)をクライアントに公開する必要がないことです。これにより、クライアントを漏洩から保護できます。

次の例は、バインド認証の構成を示しています。

バインド認証
  • Java

  • XML

  • Kotlin

@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
	LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
	factory.setUserDnPatterns("uid={0},ou=people");
	return factory.createAuthenticationManager();
}
<ldap-authentication-provider
	user-dn-pattern="uid={0},ou=people"/>
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource): AuthenticationManager {
    val factory = LdapBindAuthenticationManagerFactory(contextSource)
    factory.setUserDnPatterns("uid={0},ou=people")
    return factory.createAuthenticationManager()
}

上記の簡単な例では、提供されたパターンでユーザーのログイン名を置き換え、そのユーザーとしてログインパスワードを使用してバインドを試みることにより、ユーザーの DN を取得します。すべてのユーザーがディレクトリ内の単一のノードに保存されている場合、これは問題ありません。代わりに、ユーザーを見つけるために LDAP 検索フィルターを構成する場合は、以下を使用できます。

検索フィルターを使用したバインド認証
  • Java

  • XML

  • Kotlin

@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
	LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
	factory.setUserSearchFilter("(uid={0})");
	factory.setUserSearchBase("ou=people");
	return factory.createAuthenticationManager();
}
<ldap-authentication-provider
		user-search-filter="(uid={0})"
	user-search-base="ou=people"/>
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource): AuthenticationManager {
    val factory = LdapBindAuthenticationManagerFactory(contextSource)
    factory.setUserSearchFilter("(uid={0})")
    factory.setUserSearchBase("ou=people")
    return factory.createAuthenticationManager()
}

前に示した ContextSource 定義とともに使用すると、(uid={0}) をフィルターとして使用して、DN ou=people,dc=springframework,dc=org で検索が実行されます。この場合も、フィルター名のパラメーターの代わりにユーザーログイン名が使用されるため、ユーザー名と等しい uid 属性を持つエントリが検索されます。ユーザー検索ベースが指定されていない場合、検索はルートから実行されます。

パスワード認証を使用する

パスワード比較とは、ユーザーが提供したパスワードをリポジトリに保存されているパスワードと比較することです。これは、パスワード属性の値を取得してローカルで確認するか、LDAP の「比較」操作を実行することで実行できます。この操作では、提供されたパスワードが比較のためにサーバーに渡され、実際のパスワード値は取得されません。パスワードがランダムソルトで適切にハッシュされている場合、LDAP 比較は実行できません。

最小限のパスワード比較構成
  • Java

  • XML

  • Kotlin

@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
	LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
			contextSource, NoOpPasswordEncoder.getInstance());
	factory.setUserDnPatterns("uid={0},ou=people");
	return factory.createAuthenticationManager();
}
<ldap-authentication-provider
		user-dn-pattern="uid={0},ou=people">
	<password-compare />
</ldap-authentication-provider>
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource?): AuthenticationManager? {
    val factory = LdapPasswordComparisonAuthenticationManagerFactory(
        contextSource, NoOpPasswordEncoder.getInstance()
    )
    factory.setUserDnPatterns("uid={0},ou=people")
    return factory.createAuthenticationManager()
}

次の例は、いくつかのカスタマイズを含む、より高度な構成を示しています。

パスワード比較構成
  • Java

  • XML

  • Kotlin

@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
	LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
			contextSource, new BCryptPasswordEncoder());
	factory.setUserDnPatterns("uid={0},ou=people");
	factory.setPasswordAttribute("pwd");  (1)
	return factory.createAuthenticationManager();
}
<ldap-authentication-provider
		user-dn-pattern="uid={0},ou=people">
	<password-compare password-attribute="pwd"> (1)
		<password-encoder ref="passwordEncoder" /> (2)
	</password-compare>
</ldap-authentication-provider>
<b:bean id="passwordEncoder"
	class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource): AuthenticationManager {
    val factory = LdapPasswordComparisonAuthenticationManagerFactory(
        contextSource, BCryptPasswordEncoder()
    )
    factory.setUserDnPatterns("uid={0},ou=people")
    factory.setPasswordAttribute("pwd") (1)
    return factory.createAuthenticationManager()
}
1 パスワード属性を pwd として指定します。

LdapAuthoritiesPopulator

Spring Security の LdapAuthoritiesPopulator は、ユーザーに返される権限を決定するために使用されます。次の例は、LdapAuthoritiesPopulator の構成方法を示しています。

LdapAuthoritiesPopulator の設定
  • Java

  • XML

  • Kotlin

@Bean
LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSource) {
	String groupSearchBase = "";
	DefaultLdapAuthoritiesPopulator authorities =
		new DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase);
	authorities.setGroupSearchFilter("member={0}");
	return authorities;
}

@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource, LdapAuthoritiesPopulator authorities) {
	LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
	factory.setUserDnPatterns("uid={0},ou=people");
	factory.setLdapAuthoritiesPopulator(authorities);
	return factory.createAuthenticationManager();
}
<ldap-authentication-provider
	user-dn-pattern="uid={0},ou=people"
	group-search-filter="member={0}"/>
@Bean
fun authorities(contextSource: BaseLdapPathContextSource): LdapAuthoritiesPopulator {
    val groupSearchBase = ""
    val authorities = DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase)
    authorities.setGroupSearchFilter("member={0}")
    return authorities
}

@Bean
fun authenticationManager(
    contextSource: BaseLdapPathContextSource,
    authorities: LdapAuthoritiesPopulator): AuthenticationManager {
    val factory = LdapBindAuthenticationManagerFactory(contextSource)
    factory.setUserDnPatterns("uid={0},ou=people")
    factory.setLdapAuthoritiesPopulator(authorities)
    return factory.createAuthenticationManager()
}

Active Directory

Active Directory は、独自の非標準の認証オプションをサポートしており、通常の使用パターンは標準の LdapAuthenticationProvider にあまり適合しません。通常、認証は、LDAP 識別名を使用するのではなく、ドメインユーザー名(user@domain の形式)を使用して実行されます。これを簡単にするために、Spring Security には、一般的な Active Directory セットアップ用にカスタマイズされた認証プロバイダーがあります。

ActiveDirectoryLdapAuthenticationProvider の構成は非常に簡単です。ドメイン名とサーバーのアドレスを提供する LDAPURL を指定するだけで済みます。

DNS ルックアップを使用してサーバーの IP アドレスを取得することもできます。これは現在サポートされていませんが、将来のバージョンでサポートされる予定です。

次の例では、Active Directory を構成します。

Active Directory 構成の例
  • Java

  • XML

  • Kotlin

@Bean
ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {
	return new ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/");
}
<bean id="authenticationProvider"
        class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
	<constructor-arg value="example.com" />
	<constructor-arg value="ldap://company.example.com/" />
</bean>
@Bean
fun authenticationProvider(): ActiveDirectoryLdapAuthenticationProvider {
    return ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/")
}