本ページではユーザー認証を LDAP サーバーからの情報をもとに行います。
Spring LDAP が提供する LDAP クライアントを Spring Boot から利用します。LDAP サーバーは、Spring Boot のドキュメントに記載のある UnboundID を利用します。メモリ上で動作し、アプリケーション起動時にリソースファイルをもとに初期化される、開発時に便利な簡易サーバーです。
公式ドキュメント
LDAP について
サンプルプロジェクト
基本的な Gradle プロジェクトです。
.
|-- build.gradle
|-- gradle
| `-- wrapper
| |-- gradle-wrapper.jar
| `-- gradle-wrapper.properties
|-- gradlew
|-- gradlew.bat
`-- src
`-- main
|-- java
| `-- hello
| |-- Application.java
| |-- HelloController.java
| `-- WebSecurityConfig.java
`-- resources
|-- application.yml
`-- test-server.ldif
build.gradle
buildscript {
ext {
springBootVersion = '1.5.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
jar {
baseName = 'gs-spring-boot'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-data-ldap')
compile('org.springframework.security:spring-security-ldap')
compile('com.unboundid:unboundid-ldapsdk') // UnboundId, an open source LDAP server.
}
src/main/resources/application.yml
アプリケーション設定ファイルにアプリ内蔵の簡易 LDAP サーバ UnboundID の設定を追加します。
spring:
ldap:
embedded:
base-dn: dc=springframework,dc=org
ldif: classpath:test-server.ldif
port: 8389
src/main/java/hello/Application.java
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
src/main/java/hello/HelloController.java
HTTP レスポンスのボディで返す文字列を直接コントローラで指定する @RestController
アノテーションを利用しています。
package hello;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
public class HelloController {
@RequestMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
}
src/main/java/hello/WebSecurityConfig.java
Spring Security の利用時と同様に、@EnableWebSecurity
アノテーションが設定されて WebSecurityConfigurerAdapter
を継承したクラスで Spring Security の既定の挙動をカスタマイズできます。
- .authorizeRequests() すべてのエンドポイントで認証が必要になるように設定しています。
- .formLogin() ログイン認証用のエンドポイントが自動生成されるように設定しています。既定値のパス
/login
に対する HTML ログインフォームページが生成されます。 - .ldapAuthentication() ログイン認証で LDAP サーバからの情報を利用するように設定しています。入力されたユーザー名は
uid={0}
で LDAP 検索時に利用されます。入力されたパスワードは LdapShaPasswordEncoder() でハッシュ値を計算して、属性名userPassword
の値と比較されます。
WebSecurityConfig.java
package hello;
import java.util.Arrays;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.encoding.LdapShaPasswordEncoder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
@Configuration
@EnableWebSecurity
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()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource(contextSource())
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
}
@Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource(Arrays.asList("ldap://localhost:8389/"), "dc=springframework,dc=org");
}
}
src/main/resources/test-server.ldif
Spring LDAP チュートリアルに記載の LDIF (LDAP Data Interchange Format) 情報をそのまま利用します。ログインユーザー名は ben
、パスワードは benspassword
です。
dn: dc=springframework,dc=org
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: springframework
...
dn: uid=ben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Ben Alex
sn: Alex
uid: ben
userPassword: {SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=
...
dn
Distinguished Name → ディレクトリ内で一意に識別するための値dc
Domain Component → ドメインを構成するための要素 (例えばwww.google.com
はdc=www,dc=google,dc=com
となります)ou
Organizational Unit → あるドメイン内の組織cn
Common Name → ある組織内の人やもの
LDAP クライアントの使い方
上記サンプルプロジェクトでは、LDAP クライアントを Spring Security の認証で、あまり意識することなく内部的に利用しました。以下では、Spring LDAP が提供する LDAP クライアントを直接利用するサンプルコードをまとめます。簡単のため、HelloController.java
内で LDAP クライアントを利用しますが、実際のアプリケーションでは @Repository Bean や @Service Bean で利用する設計が好ましいとされます。
CN 一覧を検索
src/main/resources/application.yml
LDAP サーバーへの接続に必要な情報を設定します。LdapContextSource
に値が設定されて @Autowired
した LdapTemplate
で利用されます。
spring:
ldap:
embedded:
base-dn: dc=springframework,dc=org
ldif: classpath:test-server.ldif
port: 8389
context-source:
url: ldap://localhost:8389
base: dc=springframework,dc=org
username:
password:
src/main/java/hello/HelloController.java
今回のサンプルでは扱いませんが、context-source
の値を動的にリクエスト毎に設定したい場合は、LdapContextSource
を new で新規に生成して、それを引数に LdapTemplate
を new で新規に生成して利用します。LdapTemplate
の @Autowired
は行いません。
package hello;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
import java.util.List;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private LdapTemplate ldapTemplate;
public List<String> getAllPersonNames() {
return ldapTemplate.search(
query().where("objectclass").is("person"),
new AttributesMapper<String>() {
public String mapFromAttributes(Attributes attrs) throws NamingException {
return (String) attrs.get("cn").get();
}
});
}
@RequestMapping("/")
public String index() {
return getAllPersonNames().toString();
}
}
レスポンス例
[quote\"guy, Joe Smeth, Mouse, Jerry, slash/guy, Ben Alex, Bob Hamilton, Space Cadet]
1000 件を越えるデータを取得する
LDAP サーバで一度に取得可能な最大件数 1000 が設定されていることがあります。そのような場合は Paged Search Results に記載の内容を参考に、ldapsearch
コマンドの -E pr=1000/noprompt
に相当する処理を Java で行います。
DN で検索
src/main/resources/application.yml
「CN 一覧を検索」の場合と同様の設定を行います。
src/main/java/hello/HelloController.java
ディレクトリ内のエントリー識別子 DN を引数にして lookup
で検索します。
package hello;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private LdapTemplate ldapTemplate;
public String findPersonName(String dn) {
return ldapTemplate.lookup(dn, new AttributesMapper<String>() {
public String mapFromAttributes(Attributes attrs) throws NamingException {
return (String) attrs.get("cn").get();
}
});
}
@RequestMapping("/")
public String index() {
return findPersonName("uid=ben,ou=people");
}
}
レスポンス例
Ben Alex
ContextMapper
の利用
AttributesMapper
ではなく ContextMapper
を利用することもできます。以下は lookup
の例ですが search
でも同様です。
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private LdapTemplate ldapTemplate;
public String findPersonName(String dn) {
return ldapTemplate.lookup(dn, new ContextMapper<String>() {
public String mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
return context.getStringAttribute("cn");
}
});
}
@RequestMapping("/")
public String index() {
return findPersonName("uid=ben,ou=people");
}
}
関連記事
- Spring Security フォームログインのサンプルコードSpring フレームワークによる Web アプリケーション開発で、ログイン処理を実装する際は Spring Security が便利です。ここでは特に Spring Boot で Web アプリケーションを開発する場合を対象とし、フォームによる ID/Password ログインを行うためのサンプルコードをまとめます。 公式ドキュメント [Spring Security チュートリアル](http...
- Java配列の宣言方法 (C/C++との違い)Javaの配列 Javaの配列宣言方法はC/C++と似ているようで若干異なる。 初期化しない場合 C/C++の int array[10]; はJavaでは int array[] = new int[10]; となる。同様にC/C++の int array[3][3]; はJavaでは int array[][] = new int[3][3]; となる。 初期化
- PlantUML による UML 図の描き方PlantUML はテキスト形式で表現されたシーケンス図やクラス図といった UML (Unified Modeling Language) 図の情報から画像を生成するためのツールです。簡単な使い方をまとめます。 インストール方法の選択 Atom や Eclipse のプラグインをインストールしてエディタから利用する方法、JAR をダウンロードして Java コマンドで実行する方法、Redmine ...
- Akka HTTP サンプルコード (Scala)Akka アクターを用いて実装された汎用 HTTP フレームワークです。Spray の後継です。コアモジュールである akka-http-core は 2016/2/17 に experimental が外れました。akka-http などのいくつかのサブモジュールは 2016/3/1 現在 experimental のままですが、基本的な
- Kestrel の使用例Kestrel は Message Queue (MQ) の実装のひとつです。一般に MQ はアプリケーション間やプロセス間、スレッド間で非同期に通信するために用いられます。メッセージの送信側は MQ に書き込めば受信側の応答を待たずに次の処理に非同期に進むことができます。Kestrel はわずか 2500 行程の Scala で実装されており JVM で動作します。MQ 自体はメモリ上に存在する...