Spring BootでLDAP認証を行う
Spring Boot で LDAP認証を利用してみます。
Spring Security LDAPを利用することで簡単に実装できます。
今回確認に利用したプロジェクト全体は、下記に配置しています。
プロジェクト
Spring Bootの最新(2022年10月16日時点)となる2.7.4で確認します。
- Java 17
- Spring Boot 2.7.4
- Gradle 7.5
build.gradle
は下記のようになります。
plugins {
id 'org.springframework.boot' version '2.7.4'
id 'io.spring.dependency-management' version '1.0.14.RELEASE'
id 'java'
}
group = 'com.github.onozaty'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.ldap:spring-ldap-core'
implementation 'org.springframework.security:spring-security-ldap'
implementation 'com.unboundid:unboundid-ldapsdk'
}
tasks.named('test') {
useJUnitPlatform()
}
spring-boot-starter-security
にプラスして、LDAPでの認証を行うために spring-ldap-core
と spring-security-ldap
を追加する形になります。
unboundid-ldapsdk
は、確認用のLDAPサーバとして利用しています。
(別途LDAPサーバがあって、そちらと通信する形ならば不要)
UnboundID LDAP の設定
UnboundID LDAP で利用するLDIFファイルを作成します。
パスワードはBCryptでハッシュ化します。
dn: dc=example, dc=com
objectClass: top
objectClass: domain
objectClass: extensibleObject
dc: example
dn: ou=groups,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: groups
dn: cn=admin,ou=groups,dc=example,dc=com
cn: admin
objectClass: groupOfUniqueNames
objectClass: top
uniqueMember: uid=taro,ou=people,dc=example,dc=com
dn: cn=user,ou=groups,dc=example,dc=com
cn: user
objectClass: groupOfUniqueNames
objectClass: top
uniqueMember: uid=taro,ou=people,dc=example,dc=com
uniqueMember: uid=hanako,ou=people,dc=example,dc=com
dn: ou=people,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: people
dn: uid=taro,ou=people,dc=example,dc=com
cn: Taro, Yamada
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
sn: Taro
uid: taro
userPassword: $2a$08$cf6mjdn16R09lrCdQQb5NuGzi1m6g5JeTakFAfsk6dbtepoh0ckh.
dn: uid=hanako,ou=people,dc=example,dc=com
cn: Hanako, Yamada
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
sn: Hanako
uid: hanako
userPassword: $2a$08$qe6uyixO/SwhfIfQGwdOM.yt8OVcGSRESInc5dodO2vcSL51oFtJC
権限による制御も確認したかったので、admin
とuser
の2つのグループを用意し、下記の2つのユーザを用意しています。
ユーザ | パスワード | グループ |
---|---|---|
taro | taropassword | admin、user |
hanako | hanakopassword | user |
application.properties
にて、LDAPサーバの情報を指定します。
spring.ldap.embedded.base-dn=dc=example,dc=com
spring.ldap.embedded.ldif=classpath:test-server.ldif
spring.ldap.embedded.port=8389
簡単なWebページの作成
アクセスの制御を確認するため、下記の3つのHTMLを作成します。
-
src/main/resources/static/index.html
トップページ -
src/main/resources/static/admin/admin.html
admin向けのページ -
src/main/resources/static/user/user.html
user向けのページ
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Top</title>
</head>
<body>
<ul>
<li><a href="./admin/admin.html">Admin</a></li>
<li><a href="./user/user.html">User</a></li>
<li><a href="./logout">Logout</a></li>
</ul>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Admin</title>
</head>
<body>
<ul>
<li><a href="../">Top</a></li>
<li><a href="../user/user.html">User</a></li>
<li><a href="../logout">Logout</a></li>
</ul>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>User</title>
</head>
<body>
<ul>
<li><a href="../">Top</a></li>
<li><a href="../admin/admin.html">Admin</a></li>
<li><a href="../logout">Logout</a></li>
</ul>
</body>
</html>
Spring Security の設定
Spring Security の設定を行います。
SpringのGetting Started Guidesにある Authenticating a User with LDAP だと、WebSecurityConfigurerAdapter
を使った実装になっていますが、WebSecurityConfigurerAdapter
は既に非推奨になっているので注意が必要です。(Spring Security 5.7に対応した更新が漏れている?)
Spring Security側のリファレンスだと最新の情報になっているので、そちらを参考に記載します。
package com.example.ldap;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.ldap.LdapPasswordComparisonAuthenticationManagerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html
// デフォルトのフォーム認証を利用
http.formLogin();
http.logout(
// GETでもログアウトが受け付けられるように
logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout")));
// https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html
http.authorizeHttpRequests(
authz -> authz
// admin配下は要ADMINロール
.mvcMatchers("/admin/**").hasRole("ADMIN")
// user配下は要USERロール
.mvcMatchers("/user/**").hasRole("USER")
// それ以外のリクエストも全て認証必要(ロールは何でもOK)
.anyRequest().authenticated());
return http.build();
}
// https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html
@Bean
public BaseLdapPathContextSource contextSource() {
return new DefaultSpringSecurityContextSource(
"ldap://localhost:8389/dc=example,dc=com");
}
@Bean
public LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSource) {
// 権限の情報をgroupsから取得
String groupSearchBase = "ou=groups";
DefaultLdapAuthoritiesPopulator authorities =
new DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase);
// uniqueMemberが一致するものが対象
authorities.setGroupSearchFilter("uniqueMember={0}");
return authorities;
}
@Bean
public AuthenticationManager authenticationManager(
BaseLdapPathContextSource contextSource, LdapAuthoritiesPopulator authorities) {
LdapPasswordComparisonAuthenticationManagerFactory factory =
new LdapPasswordComparisonAuthenticationManagerFactory(
contextSource,
new BCryptPasswordEncoder());
// ユーザ名とパスワードのフィールドを指定
factory.setUserDnPatterns("uid={0},ou=people");
factory.setPasswordAttribute("userPassword");
factory.setLdapAuthoritiesPopulator(authorities);
return factory.createAuthenticationManager();
}
}
動作確認
Admin の taro でログインします。
AdminなのでAdminのページも問題無く表示されます。
User の hanako でログインします。
Adminのページに遷移すると、権限が無いので 403 Forbidden となります。
Discussion