CakePHPでLDAP認証


(2014/07/20) この文書は賞味期限切れです。最新の情報は CakePHPのLDAPサポートプラグイン(Yaldap2) を参照してください。

CakePHP を始めるなら、いつやるか? → 今でしょ!
今から始めるとして、バージョンはどうすれば? → 当然 2.x でしょ!
ということで初めてみたのですが、現在の職場で必須の LDAP 認証が標準ではサポートされていないようで早速挫折しました。幸い CakePHP 1.3 系の LDAP Datasource の元ネタを実装してくれた analogrithems.com の中の人が 2.x 系でも動作するプラグイン(CakePHP 2.0 Ldap Authentication)を公開されていましたので、それをご紹介します。動作確認したバージョンは CakePHP 2.3.5 です。

(2013/05/17) 初期バージョン公開

(2013/07/08) CakePHP のバージョンを 2.3.6 にして IdBroker パッチ を反映。

(2013/09/20) 一見うまく動いているように見えたのですが、debug を 2 から 0 にすると認証が通らなくなることが判明しました。このLDAPプラグインは完成度が低そうなので、逐次差分を作っていくという方法はそろそろあきらめて、必要な部分だけ取り出して改造していく再実装する方向としました。このページは残しますが、たぶんこれ以上メンテすることはないと思います。

(2013/10/01) debug 変数の値にかかわらず、DebugKit プラグインをロードしないようにすると動かなくなることが判明。 orz

例示におけるプラットフォームには CentOS 6.4(64bit) を使っており、yum update された環境です。他の OS やディストリビューションをお使いの方は、yumなど固有のコマンド名を適宜読み替えてください。

CakePHP のインストール

こちらに移動しました。

CakePHP 2.0 Ldap Authenticationの導入

入手

# cd app/Plugin
# git clone -b dev_cake2.0 https://github.com/analogrithems/idbroker.git
# mv idbroker Idbroker

CakePHP 2.x の規約に従ってディレクトリ名を変更しておく必要があります。

パッチの適用

CakePHP 2.0 Ldap Authenticationにはいくつかバグがありますので、以下の手順で IdBroker パッチ集 からパッチを入手して適用してください。。

# cd Idbroker
# wget http://net-newbie.com/IdBroker/all-in-one.patch
# patch -p1 < all-in-one.patch

導入後の概観

後述の設定を適用後、全体の diff を取ると以下のようになりました

$ diff -qr --exclude='tmp' --exclude='Idbroker' --exclude='debug_kit' cakephp-2.3.6 as-user
ファイルcakephp-2.3.6/app/Config/bootstrap.phpとas-user/app/Config/bootstrap.phpは違います
ファイルcakephp-2.3.6/app/Config/core.phpとas-user/app/Config/core.phpは違います
as-user/app/Configだけに発見: database.php
as-user/app/Configだけに発見: ldap.php
ファイルcakephp-2.3.6/app/Controller/AppController.phpとas-user/app/Controller/AppController.phpは違います
as-user/app/Controllerだけに発見: UsersController.php
as-user/app/Modelだけに発見: User.php
as-user/app/Viewだけに発見: Users

それでは、個別に差分を見ていきます。

(app/Plugin/Idbroker 配下に楽しそうなものが一揃い入っているみたいですが、現時点ではまだ動かせていません。本ページでは LdapAuth が通るところまでを解説しています。)

app/Config/bootstrap.php - プラグインを読み込む設定

app/Config/bootstrap.php の末尾に以下を追加します。

CakePlugin::load('Idbroker');   //  app/Plugin/Idbroker を読み込むため
Configure::load('ldap');        //  app/Config/ldap.php を読み込むため

app/Config/core.php

ハッシュ値を変えただけなので省略

app/Config/database.php - LDAP データソース

database.php はデフォルトでは存在しないので database.php.default をコピーして作るか1から作成します。

$ cat  as-user/app/Config/database.php
<?php
class DATABASE_CONFIG {

    public  $ldap = array(
        'datasource'=> 'Idbroker.LdapSource',
        'host'      => 'ldap.example.com',
        'port'      => 389,
        'basedn'    => 'dc=example,dc=com',
        'login'     => 'cn=Manager,dc=example,dc=com',  //  管理者ユーザID
        'password'  => 'naisho',                        //  管理者パスワード
        'database'  => '',
        'tls'       => false,
        'type'      => 'OpenLDAP',
        'version'   => 3
    );
}

app/Config/ldap.php

後述の User モデル(app/Model/User.php)で定義する $useDbConfig 経由で読み込まれるものです。ファイルは1から作成します。

$ cat as-user/app/Config/ldap.php
<?php
$config['LDAP']['Db']['Config'] = 'ldap';

app/Controller/AppController.php

コントローラの基底クラス。認証用フィルタをアプリケーション全体に適用するために、ここで定義します。

$ cat as-user/app/Controller/AppController.php
class   AppController   extends Controller {
    public  $components =
        array('Auth', 'Session', 'RequestHandler', 'DebugKit.Toolbar');

    //  コンポーネントの startup() より先に実行されるフィルタ
    //  (認証がかかっていなくても実行される)
    public  function    beforeFilter()  {
        $this->Auth->authenticate = array(
            'Idbroker.Ldap' => array('userModel'=>'Idbroker.LdapAuth'));
        $this->Auth->authError = 'ユーザ認証が必要です。';
        //  権限判定ハンドラ:認証処理をコントローラ(isAutorized)で行う
        $this->Auth->authorize = array('Controller');
    }

    //  グループで制限をかける場合はここを変更する
    //  現在は、認証が通ったユーザはすべて有効。
    public  function    isAuthorized(){
        $user = $this->Auth->user();    //  ログイン済みならユーザ情報が返る
        if ($user)  {
            return true;    //  認証通過済み
        }
        return false;       //  認証通過前
    }
}   //  class AppController extends Controller

app/Controller/UsersController.php

Users コントローラ。認証前の処理のみ記載しています。認証通過後は index() アクションが実行されます。

$ cat as-user/app/Controller/UsersController.php
<?php
Class   UsersController    Extends     AppController
{
    public  $name = 'Users';    //  クラス名

    /*
     * auth にアクセスする必要のない関数群を列記
     */
    public  function    beforeFilter(){
        $this->Auth->allow(
            'usernameExists', 'forgotPassword', 'signup','login','logout');
        parent::beforeFilter();
    }

    public  function    login(){
        if ($this->request->is('post')) {
            if ($this->Auth->login()) {
                return $this->redirect($this->Auth->redirect());
            } else {
                $this->Session->setFlash(
                __('ユーザ名またはパスワードが違います'),
                'default', array('class'=>'error-message'), 'auth');
            }
        }
    }

    public  function    logout(){
        $this->log('Destroying session','debug');
        $this->Session->destroy();
        $this->redirect($this->Auth->logout());
    }

    public  function    index() {
    }
}

app/Model/User.php

User モデル。ldap との関連付けを行なっています。

$ cat as-user/app/Model/User.php
<?php
/*
 * app/Model/User.php
 */

class   User    extends AppModel {
    public  $name        = 'User';
    public  $useDbConfig = 'ldap';   //  app/Config/database.php のメンバ変数名
    public  $primaryKey  = 'uid';
    public  $useTable    = 'ou=Users';
}

app/View/Users/login.ctp

ログイン画面のためのビュー。デフォルトで英語になっているところを日本語メッセージに変えています。Users ディレクトリを作ってからその中にファイルを作成します。

$ cat as-user/app/View/Users/login.ctp
<div id='loginForm'>
<?php
    echo $this->Session->flash('auth');
    echo $this->Form->create('Users', array('action' => 'login'));
    echo $this->Form->input('username');
    echo $this->Form->input('password',array('value'=>''));
    echo $this->Form->input('remember', array('type' => 'checkbox',
        'label' => 'しばらくの間、次回の認証をスキップする'));
    echo $this->Form->submit('Login');
?>
</div>

使い方

初めてログインする場合

http://as-user.example.com にアクセスすると、/users/login にリダイレクトされて以下の認証ページが表示されます。

ldap-auth-init

IDとパスワードを入力すると LDAP を引きに行って、認証が通ったら元々のトップページが表示されます。

ldap-auth-success

app/View/Pages/home.ctp で何か警告が出ています。最新の CakePHP との組み合せで何か不整合があるのかもしれません。分かったらページを更新しておきます。

おまけ

ちょっとびっくりしたのは、ブラウザを一旦閉じてもう一度トップページにアクセスすると、今度は認証を聞かれなくなることです。クッキーを食わされているのだと推定して調べてみます。

右上のハードディスクのスピンドルみたいなアイコンをクリックしてデバッグキットを表示し、Session 配下を見てみると出るわ出るわ。

ldap-auth-session

デバッグキットでクッキーを表示するにはアプリケーションのソースに少し仕掛けを入れないといけないみたいなので、ブラウザの機能で確認してみます。

ldap-auth-cookie

クッキーの有効期限はデフォルトで4時間になってるっぽいので、「ブラウザを閉じるまで」に変更するにはどうすればいいか、これから勉強します(汗)。

(2013/05/19 追記) 以下の記述を追加することで「ブラウザを閉じるまで」に変更できました。Thanks > @dataich

$ diff as-user.good/app/Config/core.php as-user/app/Config/core.php
191c191,192
<         'defaults' => 'php'
---
>         'defaults'  => 'php',
>         'timeout'   =>  0

Go previous