Jの衝動書き日記

さらりーまんSEの日記でございます。

SpringBootでLDAP認証-LDAP認証をおこなうアプリケーションの作成-

SpringBootでLDAP認証は以下の構成となっている。

  1. LDAPサーバの構築
  2. 認証用情報の設定
  3. LDAP認証をおこなうアプリケーションの作成

 

LDAP認証をおこなうアプリケーションの作成

 その前に前提として。
 ここで使用するSpringBootのバージョンは1.5.12である。最新バージョンではないため多少手順が異なるかもしれない。
 また開発用ツールはSpringToolSuite(STS)を使っている。

 

1. SpringBootでWebアプリケーションを作成する

 LDAP認証機能の検証をおこなうため検証用のアプリケーションを作成する。作成するのはWebアプリケーションでアクセスしたら単にこんにちは!を出すだけのものである。
 面倒くさいので作成物に関する詳細は省くが以下のような手順で作成する。

  1. File>New>Spring Starter Project>Next:Name,Package,Groupなどは好きなように入力
  2. Web>Web, Template Engines>Thymeleafを選択してFinish
  3. Controllerを作成する
    package jp.example.test;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    
    @Controller
    public class HelloController {
    
        @GetMapping("/")
        public String showHello() {
           return "hello";
        }
    }
    
  4. Viewを作成する
    cat src/main/resources/templete/hello.html
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Hello</title>
    </head>
    <body>
    	<p>こんにちは!</p>
    </body>
    </html>
    

 

  (プロジェクト)>Run As>Spring Boot Appでアプリケーションを起動する。
 localhost:8080にアクセスしHello画面が表示されればOK。

 

2.アプリケーションにLDAP認証機能を付ける。

 ここではアクセスしたときにログイン画面を最初に表示し認証が成功したときのみ次へ進めるようにする。またHello画面では認証したユーザ名(sn)を表示するようにする。

 

 まずは必要なライブラリを追加する。pom.xmlのdependenciesに以下を追加する。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>

 

  次にLDAP認証のための設定をおこなう。設定のためのConfigクラスを作成する。
 ここでは認証機能の有効化、ログイン・ログアウト機能の有効化、LDAP認証の設定をおこなう。

package jp.example.test;

import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.userdetails.PersonContextMapper;


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").authenticated() /* 全てのパスに対して「認証済み」を要求するアクセスポリシーを設定 */
                .and()
                .logout().permitAll()  /* ログアウト機能を有効化  + ログアウトのURL(/logout)は認証なしでアクセス可能 */
                .and()
                .formLogin().permitAll(); /* フォームログイン機能を有効化 * ログインのURL(/login)は認証なしでアクセス可能 */
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /* LDAP認証を有効化 */
        auth.ldapAuthentication()
                .userDnPatterns("uid={0},ou=people")  /* ユーザDNのパターンを指定  {0}にはログインフォームで入力したusernameが埋め込まれる*/
                .contextSource()
                    .url("ldap://192.168.1.10:389/dc=test,dc=example,dc=jp") /* LDAPサーバへの接続URL*/
                .and()
                .userDetailsContextMapper(new PersonContextMapper());  /* 取得したユーザ情報の設定方法を指定 */
    }
}

 LDAP認証のためには以下の情報が必要となる。

  • LDAPサーバへの接続URL(ldap://{IPアドレス}:{ポート}/{ベースDN}):ldap://192.168.1.10:389/dc=test,dc=example,dc=db
  • ユーザDNのパターン:uid={0},ou=people

 ユーザDNのパターンはユーザDNからベースDNを除いた形で指定する。また具体的なユーザ名に関しては自動生成されるログイン画面より渡されるためそれを{0}で記述する。このようにユーザDNはパターンで指定するためuid以外でユーザDNを定義しても問題はない。
 また、Hello画面でユーザ名(cn)を表示するための設定もおこなう。LDAP認証機能を有効にすると認証したユーザ情報はセッション上に保存される。その保存されるデータの形式を指定する必要がある。
 org.springframework.security.ldap.userdetails.Personを使うとユーザ名(cn)を取得できるためユーザ情報をPersonの形で保存するようにPersonContextMapperを指定する。

 

最後にHello画面を変更する。

diff hello.html.bk hello.html
3c3,4
<       xmlns:th="http://www.thymeleaf.org">
---
>       xmlns:th="http://www.thymeleaf.org"
>       xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
9c10,13
<       <p>こんにちは!</p>
---
>       <p>こんにちは!<span sec:authentication="principal.cn[0]">名無し</span> さん</p>
>       <form th:action="@{/logout}" method="post">
>               <button>ログアウト</button>
>       </form>

 thymeleaf-extras-springsecurity4を使用するとセッション上に保存されているユーザ情報を上記の形で取得できる。principalにはPersonが設定されている。
 ちなみにSpringSecurityでは様々な認証方法を提供しているためprincipalに格納されるオブジェクトはその認証方法によって異なる。
 このPersonをController等で取得したい場合は以下のように取得する。

Person person = (Person)SecurityContextHolder.getContext().getAuthentication().getPrincipal();

 これでlocalhost:8080にアクセスするとHello画面は表示されずログイン画面が表示されるはずだ。UserにユーザDNのうちのuidを入力しPasswordにそのユーザのパスワードを入力してloginを押すとHello画面が表示される。そのとき画面にはユーザ名(cn)が表示されているはずである。

 

3.ユーザ権限に基づいたアクセス制御をアプリケーションに提供する

 現在のアプリケーションは認証が通ったユーザはすべてのURLへアクセスできる(といっても機能があるのは/ぐらいだが)。
 ここでは特定の権限を持つユーザのみがアクセスできる設定をおこなう。具体的にはadmin権限を持つユーザのみが/adminへアクセスできるようにする。

 

 まずは/adminの機能を作成する。とはいってもこれは適当でいい。HelloControllerをコピーしてGetMappingの内容を/adminにする程度で構わない。面倒ならばなくてもいい(表示結果が404になるのでちょっと微妙だが)。


 次にアクセス設定をおこなう。
 作成済のConfigクラスにアクセスを制限するURLとそれを利用可能なユーザの権限名の指定、LDAPサーバからユーザが持つ権限を取得するための設定をおこなう。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin", "/admin.*", "/admin/**").hasRole("ADMIN") /*/admin配下にアクセスするためにはadmin権限が必要 */
            .antMatchers("/**").authenticated() 
            .and()
            .logout().permitAll() 
            .and()
            .formLogin().permitAll();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.ldapAuthentication()
            .userDnPatterns("uid={0},ou=people")
            .groupSearchBase("ou=role")                            /* 権限の格納先を指定 */
            .contextSource()
                .url("ldap://192.168.1.10:389/dc=test,dc=example,dc=jp")
            .and() 
            .userDetailsContextMapper(new PersonContextMapper());
}

 

 先のLDAP認証の設定では認証のみをおこなっていたが権限に基づいたアクセス制限までをおこないたい場合は以下の設定が必要となる。

  • LDAPサーバへの接続URL(ldap://{IPアドレス}:{ポート}/{ベースDN}):ldap://192.168.1.10:389/dc=test,dc=example,dc=db
  • ユーザDNのパターン:uid={0},ou=people
  • 権限の検索先の指定:ou=role

 権限の格納先に関してはベースDNを除いた形で指定する。LDAPサーバの設定によっては管理者DNとそのパスワードの指定も必要かもしれない。その場合は上記の.urlの後に.managerDnと.managerPasswordで設定する。
 アクセス制限の設定は上記のとおり.antMatchersで指定する。hasRoleで権限名を指定する。LDAPサーバ上の設定ではadminと小文字だがSpringSecurityでは大文字となる。
 ちなみにユーザが持つ権限情報もセッション上に保存されている。格納先はユーザ情報と同様にPersonでgetAuthorities()でそれが取得できる。ここではROLE_ADMINのような形で値が取れるがPrefixに関してはカスタマイズ可能のようだ。

 これでlocalhost:8080/adminでアクセスするとuser:gokuuのときは正常に表示され(/admin未実装ならば404のエラー表示)、user:gohanの場合は403のエラーが表示されるようになる。
 ちなみにリソース類のアクセスについてはどうなのかというとデフォルトでは認証不要となっている。具体的には/js,/css,/imagesなどは認証不要となっている。

 

4. ログイン・ログアウト機能をカスタマイズする

 自動的に作成されるログイン・ログアウト機能だがカスタマイズも可能だ。ここでは概要のみの記述に留める。

  • ログイン機能
    • ログイン画面の表示
      • デフォルトは/login(GET)
    • ログイン成功時の遷移先URL
      • デフォルトではアクセス時のURL
      • 初期ログイン後に最初のページに遷移させたいときなどに使用
    • ログイン失敗時の遷移先URL
      • デフォルトは/login?error
    • ログイン成功後のハンドリング
      • ログイン成功後に実行したい処理があるときなど
      • 上記の遷移先URLが設定済の場合はそっちが優先される
      • 応答内容の作成、もしくはリダイレクト処理が必要
    • ログイン失敗後のハンドリング
      • 認証NGに出すエラー内容をカスタマイズしたいときなど
      • デフォルトではエラー発生時に例外はスローしない
      • エラー原因はAuthenticationExceptionで特定が可能
  • ログアウト機能
    • ログアウトの実行URL
      • デフォルトでは/logout(POST)
    • ログアウト成功時の遷移先URL
      • デフォルトは/login?logout
    • ログアウト実施時の挙動
      • セッション消去・Cookie削除など

 

参考

qiita.comここではDockerを使ってLDAPサーバを構築している。

ログインのカスタマイズ方法

ログアウトのカスタマイズ方法