こちらのページでは、Java のソースコードにハードコーディングしたユーザーとパスワードの情報をもとに、Spring Security でログインフォーム認証を行いました。本ページではユーザー認証を LDAP サーバーからの情報をもとに行います。
Spring LDAP が提供する LDAP クライアントを Spring Boot から利用します。LDAP サーバーは、Spring Boot のドキュメントに記載のある UnboundID を利用します。メモリ上で動作し、アプリケーション起動時にリソースファイルをもとに初期化される、開発時に便利な簡易サーバーです。
公式ドキュメント
基本的な 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
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.
}
アプリケーション設定ファイルにアプリ内蔵の簡易 LDAP サーバ UnboundID の設定を追加します。
spring:
ldap:
embedded:
base-dn: dc=springframework,dc=org
ldif: classpath:test-server.ldif
port: 8389
アノテーションの意味についてはこちらをご参照ください。
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);
}
}
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!";
}
}
Spring Security の利用時と同様に、@EnableWebSecurity
アノテーションが設定されて WebSecurityConfigurerAdapter
を継承したクラスで Spring Security の既定の挙動をカスタマイズできます。
/login
に対する HTML ログインフォームページが生成されます。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");
}
}
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 クライアントを Spring Security の認証で、あまり意識することなく内部的に利用しました。以下では、Spring LDAP が提供する LDAP クライアントを直接利用するサンプルコードをまとめます。簡単のため、HelloController.java
内で LDAP クライアントを利用しますが、実際のアプリケーションでは @Repository Bean や @Service Bean で利用する設計が好ましいとされます。
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:
今回のサンプルでは扱いませんが、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]