実証環境
- OS
- Amazon Linux 2
- PHP
- 7.2.34
- Laravel
- 7.30.4
- LDAP
- OpenLDAP 2.4.44
環境構築
OpenLDAP
yum を使ってインストール
$ sudo yum -y install openldap-clients openldap-servers
設定ファイルを用意
$ sudo cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/
$ sudo chown ldap.ldap /var/lib/ldap/DB_CONFIG
起動と自動起動設定
OpenLDAP サーバーの起動と、自動起動を有効化する
$ sudo systemctl start slapd
$ sudo systemctl enable slapd
OpenLDAP サーバーの起動設定が有効化できているかの確認をする
$ sudo systemctl is-enabled slapd
enabled
OpenLDAP サーバーが LISTEN しているかを確認する
$ netstat -atnl | grep ':389.*LISTEN'
tcp 0 0 0.0.0.0:389 0.0.0.0:* LISTEN
tcp6 0 0 :::389 :::* LISTEN
LDAP リポジトリの実装
管理者パスワードを設定する
slappasswd
を使って OpenLDAP 管理者パスワードを生成する(後ほど利用する)
$ slappasswd
New password:
Re-enter new password:
{SSHA}ALgM7/V/tIQv53Dx4KSpGY6RW4AtnRPS
管理者アカウント用の LDIF を用意する(パスは任意だが分かりやすいように ${HOME} 環境変数直下に ldif ディレクトリを作成した)
${HOME}/ldif/add-root-account.ldif
dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}ALgM7/V/tIQv53Dx4KSpGY6RW4AtnRPS
ldapadd
コマンドで LDAP サーバー(slapd
)へ反映
$ ldapadd -Y EXTERNAL -H ldapi:/// -f ${HOME}/ldif/add-root-account.ldif
ドメインリポジトリ(example.com)を実装する
ドメインリポジトリの実装を進める前提として、リポジトリマネージャーのパスワードを slappasswd
で設定し、LDIF で利用する
$ slappasswd
New password:
Re-enter new password:
{SSHA}UESMaOwll1zRkb15o9IiSm27bS0CZ+bC
${HOME}/ldif/add-repository-manager.ldif
dn: olcDatabase={1}monitor,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"
read by dn.base="cn=Manager,dc=example,dc=com" read by * none
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=example,dc=com
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcRootDN
olcRootDN: cn=Manager,dc=example,dc=com
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}UESMaOwll1zRkb15o9IiSm27bS0CZ+bC
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to attrs=userPassword,shadowLastChange by
dn="cn=Manager,dc=example,dc=com" write by anonymous auth by self write by * none
olcAccess: {1}to dn.base="" by * read
olcAccess: {2}to * by dn="cn=Manager,dc=example,dc=com" write by * read
ldapadd
コマンドで LDAP サーバー(slapd
)へ反映
$ ldapadd -Y EXTERNAL -H ldapi:/// -f ${HOME}/ldif/add-repository-manager.ldif
ドメインリポジトリを実装するため、${HOME}/ldif/add-dc=example,dc=com.ldif
を用意する
${HOME}/ldif/add-dc=example,dc=com.ldif
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectclass: organization
o: example
dc: example
dn: cn=Manager,dc=example,dc=com
objectClass: organizationalRole
cn: Manager
description: Directory Manager
dn: ou=People,dc=example,dc=com
objectClass: organizationalUnit
ou: People
dn: ou=Group,dc=example,dc=com
objectClass: organizationalUnit
ou: Group
ldapadd
コマンドで LDAP サーバー(slapd
)へ反映
$ ldapadd -x -D cn=Manager,dc=example,dc=com -w ${YOUR_REPOSITORY_MANAGER_PASSWORD} -f ${HOME}/ldif/add-dc=example,dc=com.ldif
LDAP 認証連携用のユーザーを登録する
認証試験用のユーザーパスワードを slappasswd
で設定し、LDIF で利用する
$ slappasswd
New password:
Re-enter new password:
{SSHA}twe1y8JFGBVJv9fLtl62oKj6wwt5q8WZ
${HOME}/ldif/dc=example,dc=com/ou=People/add-user.ldif
dn: uid=user,ou=People,dc=example,dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: user
cn: user
sn: user
o: People
userPassword: {SSHA}twe1y8JFGBVJv9fLtl62oKj6wwt5q8WZ
-
userPassword
は1qazxsw23edc
で設定(各位の環境に合わせること)
ldapmodify
コマンドで LDAP サーバー(slapd
)へ反映
$ ldapadd -x -D cn=Manager,dc=example,dc=com -w ${YOUR_REPOSITORY_MANAGER_PASSWORD} -f ${HOME}/ldif/dc=example,dc=com/ou=People/add-user.ldif
Laravel
php-ldap をインストールする
Laravel から LDAP サーバーへアクセスするために php-ldap
拡張モジュールをインストールする必要があるため、yum intall
でインストールする
$ sudo yum -y install php-ldap
Composer を使ってプロジェクトを作成する
$ composer create-project --prefer-dist laravel/laravel ldap-record-laravel-passport
LdapRecord-Laravel
Composer を使ってインストール
- APP_ROOT へ cd してから LdapRecord-Laravel パッケージをインストールする
$ cd ldap-record-laravel-passport/
$ composer require directorytree/ldaprecord-laravel
- artisan コマンドを使って設定ファイルを publish する
$ php artisan vendor:publish --provider="LdapRecord\Laravel\LdapServiceProvider"
- ldap.php が publish されていることを確認
$ ls -1 config/ldap.php
config/ldap.php
- artisan コマンドを使って認証用データベースマイグレーションファイルを publish する
$ php artisan vendor:publish --provider="LdapRecord\Laravel\LdapAuthServiceProvider"
- マイグレーションファイルが publish されていることを確認
$ ls -1 database/migrations/
2021_01_22_065947_add_ldap_columns_to_users_table.php
-
YYYY_MM_DD_HHMMSS は、実行タイミングによる
-
artisan コマンドで migrate する
$ php artisan migrate
各種設定
- .env ファイルへ以下の設定を追加する
LDAP_LOGGING=true
LDAP_CONNECTION=default
LDAP_HOST=127.0.0.1
LDAP_USERNAME="cn=Manager,dc=example,dc=com"
LDAP_PASSWORD=${YOUR_REPOSITORY_MANAGER_PASSWORD}
LDAP_PORT=389
LDAP_BASE_DN="dc=example,dc=com"
LDAP_TIMEOUT=5
LDAP_SSL=false
LDAP_TLS=false
- APP_ROOT/app/User.php へ trait と interface を追記する
use LdapRecord\Laravel\Auth\LdapAuthenticatable;
use LdapRecord\Laravel\Auth\AuthenticatesWithLdap;
class User extends Authenticatable implements LdapAuthenticatable
{
use Notifiable, AuthenticatesWithLdap;
- config/auth.php に LdapRecord 連携へ設定変更する
'providers' => [
'users' => [
'driver' => 'ldap',
'model' => LdapRecord\Models\OpenLDAP\User::class,
'rules' => [],
'database' => [
'model' => App\User::class,
'sync_passwords' => false,
'sync_attributes' => [
'name' => 'cn',
'email' => 'mail',
],
],
],
],
Laravel Passport
データベースの用意する
sqlite
を利用するので、データベースファイルを作成後に .env
ファイル修正
$ touch database/database.sqlite
- .env
DB_CONNECTION=sqlite
Composer を使ってインストール
$ composer require laravel/passport
- artisan コマンドで migrate する
$ php artisan migrate
設定変更
修正後の全体ファイルを掲載する
APP_ROOT/app/User.php
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use LdapRecord\Laravel\Auth\LdapAuthenticatable;
use LdapRecord\Laravel\Auth\AuthenticatesWithLdap;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements LdapAuthenticatable
{
use HasApiTokens, Notifiable, AuthenticatesWithLdap;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
APP_ROOT/app/Providers/AuthServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
検証
トークン生成
artisan tinker
を開き、Auth::attempt()
メソッドを LDAP へ登録したユーザーとパスワードで実行の後、Auth::user();
を利用してインスタンス化
>>> Auth::attempt(['uid' => 'user', 'password' => '1qazxsw23edc'], true)
=> true
>>> $user = Auth::user();
=> App\User {#4311
id: "1",
name: "user",
email: "user@example.com",
email_verified_at: null,ACA
created_at: "2021-01-22 07:36:07",
updated_at: "2021-01-22 07:36:07",
guid: "928a247a-e98e-103a-998b-b183875012cd",
domain: "default",
}
インスタンスから createToken('ANY_NAME')->accessToken;
メソッドを呼び出しトークンを取得する
>>> $user->createToken('foobar')->accessToken;
=> "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiMmRmYTgzZWEwNTI1OTBjMjQyZTEyNzZmNGY2MWIxODJlOWQ3M2U4M2VkMDk3MjE4ZGE5YjA3MGY0NWY1YjY2NGE5MTIwYzQwNzMyY2UyZjEiLCJpYXQiOjE2MTE1NTM5MDAsIm5iZiI6MTYxMTU1MzkwMCwiZXhwIjoxNjQzMDg5OTAwLCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.KP2JVn4KHAHKgmFi1w12mwY5bzYZnCAbJ0HcMOPwIORtFfHRuPqrr0BjlHQzkJGRvXNutRrRa3jQUqyFrv61G2RxNEfpv4aq8PWC7x8zduC6M94yBzNLqPTLPLZDMeMe_STseCjGsMcGmlRoA_Uf-jK17SRDLppDiQPTwFZ1N_w88DMCt_tmh25zHfum4vJaP_ZMIfEHvRJmOSkySLGAHuYwHtN7tiZ8pAQ6w_B8mTXZ3tmbfHPfk5Fo_hqCTTDauFKKgRWJowEW6_qEcO7xXPOmX5jVXI9j_CFl6Nqk2-INhUTk_lYP5-ughNTuftjacLNubk5GulG0Qf2D6AyyXyryieUNa1XVK35CEb_CumB1D-7I9LWfH9hzqK9DHU9BcD-06DCwB-6YO6i-QWMOudJMkuu9Yx3FFnyuvHHvEIaEPq0NA_gmDYcyQSUmyXu_Ii_jtWB-bZgNxneSo5va_bOCo8GEzCd-FAa4Lm6WmghRxJlo9O6l0oDEqaJ69IXUzjyaYSSQDuK5hkX4jJK17tCpJ2GUN0clsVF7G9KMiAR73O8EcjkqVn2BcpQK-0SromPw9NAgbBpaJuMi7Kv-kCRThh8BSHuMDwg_kNkwV2uaM4aIbaVx2ZwsvIYWPzU9G_TplpdBnNgwCpat7v1NRCDoQHBhTQ5dIPo13QtNaEQ"
- トークンは環境ごとに異なる
トークンを使って認証後にデータを取得する
まず、アクセス用プログラムを実装する(GuzzleHttp
を利用した)
- access.php
<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client;
$accessToken = getenv('ACCESS_TOKEN');
$response = $client->request('GET', 'http://127.0.0.1:8000/api/user', [
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $accessToken,
],
]);
var_dump(json_decode((string) $response->getBody(), true));
環境変数 ACCESS_TOKEN
へトークンを設定後に、自身のデータを取得できることを確認する
$ export ACCESS_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiMmRmYTgzZWEwNTI1OTBjMjQyZTEyNzZmNGY2MWIxODJlOWQ3M2U4M2VkMDk3MjE4ZGE5YjA3MGY0NWY1YjY2NGE5MTIwYzQwNzMyY2UyZjEiLCJpYXQiOjE2MTE1NTM5MDAsIm5iZiI6MTYxMTU1MzkwMCwiZXhwIjoxNjQzMDg5OTAwLCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.KP2JVn4KHAHKgmFi1w12mwY5bzYZnCAbJ0HcMOPwIORtFfHRuPqrr0BjlHQzkJGRvXNutRrRa3jQUqyFrv61G2RxNEfpv4aq8PWC7x8zduC6M94yBzNLqPTLPLZDMeMe_STseCjGsMcGmlRoA_Uf-jK17SRDLppDiQPTwFZ1N_w88DMCt_tmh25zHfum4vJaP_ZMIfEHvRJmOSkySLGAHuYwHtN7tiZ8pAQ6w_B8mTXZ3tmbfHPfk5Fo_hqCTTDauFKKgRWJowEW6_qEcO7xXPOmX5jVXI9j_CFl6Nqk2-INhUTk_lYP5-ughNTuftjacLNubk5GulG0Qf2D6AyyXyryieUNa1XVK35CEb_CumB1D-7I9LWfH9hzqK9DHU9BcD-06DCwB-6YO6i-QWMOudJMkuu9Yx3FFnyuvHHvEIaEPq0NA_gmDYcyQSUmyXu_Ii_jtWB-bZgNxneSo5va_bOCo8GEzCd-FAa4Lm6WmghRxJlo9O6l0oDEqaJ69IXUzjyaYSSQDuK5hkX4jJK17tCpJ2GUN0clsVF7G9KMiAR73O8EcjkqVn2BcpQK-0SromPw9NAgbBpaJuMi7Kv-kCRThh8BSHuMDwg_kNkwV2uaM4aIbaVx2ZwsvIYWPzU9G_TplpdBnNgwCpat7v1NRCDoQHBhTQ5dIPo13QtNaEQ"
$ php access.php
array(8) {
["id"]=>
int(1)
["name"]=>
string(7) "user"
["email"]=>
string(18) "user@example.com"
["email_verified_at"]=>
NULL
["created_at"]=>
string(27) "2021-01-22T07:36:07.000000Z"
["updated_at"]=>
string(27) "2021-01-22T07:36:07.000000Z"
["guid"]=>
string(36) "928a247a-e98e-103a-998b-b183875012cd"
["domain"]=>
string(7) "default"
}