mosakabe @ ウィキ
Cookieエージェント型SSOを構築
最終更新:
mosakabe
-
view
LDAPを利用して、WebサーバでCookieエージェント型SSOを構築する。
LDAPサーバ、Loginサーバ、(認証対象の)Webサーバを用意する。
LDAPサーバを ldap.totto.local
Loginサーバを login.totto.local
Webサーバを web.totto.local
とする。
LDAPサーバを ldap.totto.local
Loginサーバを login.totto.local
Webサーバを web.totto.local
とする。
LDAPサーバ構築
LDAPサーバの構築はこちら
ログインサーバ側構築
ログインサーバを用意する。
$ sudo apt-get install php5-ldap
RSA鍵ペアを作る
$ sudo mkdir -p /etc/ldap-login-keypair/ $ cd /etc/ldap-login-keypair/ $ sudo openssl genrsa -out privkey.pem 1024 $ sudo openssl rsa -in privkey.pem -out pubkey.pem -pubout $ sudo chown www-data privkey.pem $ sudo chmod 400 privkey.pem
privkey.pem pubkey.pem ができる。
LDAP認証プログラムをつくる
$ sudo mkdir -p /usr/share/php/
※/etc/php5/apache2/php.ini の include_path に上記ディレクトリを含める
/usr/share/php/LdapAuth.php
<?php require_once('pubtkt.inc'); class LdapAuthException extends Exception{} class LdapUserOrPasswordInvaildException extends Exception{} class LdapAuth{ const PRIVATE_KEY_PATH = '/etc/ldap-login-keypair/privkey.pem'; const EXPIRE_SECOND = 86400; const KEY_TYPE = 'RSA'; const LDAP_PROTOCOL_VERSION = 3; const URI = 'ldap://ldap.totto.local'; const LDAP_DC = 'dc=example,dc=com'; const ADMIN = 'admin'; private $adminPass = 'secret'; public function __construct(){ $this->ldapConn = ldap_connect(self::URI); if(!$this->ldapConn) throw new LdapAuthException("LDAP connect failed."); ldap_set_option($this->ldapConn, LDAP_OPT_PROTOCOL_VERSION, self::LDAP_PROTOCOL_VERSION); } public function cert($userId, $pass){ $entry = $this->_getEntry($userId); $dn = $entry['dn']; $expire = time() + self::EXPIRE_SECOND; if(!@ldap_bind($this->ldapConn, $dn, $pass)) throw new LdapUserOrPasswordInvaildException('Password invalid.'); $tiket = pubtkt_generate(self::PRIVATE_KEY_PATH, self::KEY_TYPE, $userId, null, $expire, null, '', $dn); return $tiket; } private function _getEntry($userId){ $dn = 'cn='.self::ADMIN.','.self::LDAP_DC; $ldapBind = @ldap_bind($this->ldapConn, $dn, $this->adminPass); if(!$ldapBind) throw new LdapAuthException("LDAP Admin bind failed."); $ldapSearch = ldap_search($this->ldapConn, self::LDAP_DC, "uid=".$userId); $entries = ldap_get_entries($this->ldapConn, $ldapSearch); $count = isset($entries['count'])?$entries['count']:null; if($count!=1) throw new LdapUserOrPasswordInvaildException('Invalid user. User count:'.$count); return $entries[0]; } } ?>
公開鍵認証プログラムをつくる
後ででてくるWebサーバ構築時の mod_auth_pubtkt に含まれる mod_auth_pubtkt/php-login/pubtkt.inc コードそのまま。
/usr/share/php/pubtkt.inc
/usr/share/php/pubtkt.inc
<?php /* Generate tickets for use with mod_auth_pubtkt (https://neon1.net/mod_auth_pubtkt) written by Manuel Kasper <mk@neon1.net> */ /* Set this to the path to your OpenSSL binary. This is usually something like /usr/bin/openssl on Unix-like systems. On Windows, you must manually get openssl.exe *and* the necessary libraries (usually libeay32.dll and ssleay32.dll) and put them together in a directory where they're accessible to PHP. */ define("OPENSSL_PATH", "/usr/bin/openssl"); /* Generate a ticket. Parameters: privkeyfile path to private key file (PEM format) privkeytype type of private key ("RSA" or "DSA") uid user ID/username clientip client IP address (optional; can be empty or null) validuntil expiration timestamp (e.g. time() + 86400) tokens comma-separated list of tokens (optional) udata user data (optional) Returns: ticket string, or FALSE on failure */ function pubtkt_generate($privkeyfile, $privkeytype, $uid, $clientip, $validuntil, $graceperiod, $tokens, $udata) { /* format ticket string */ $tkt = "uid=$uid;"; if ($clientip) $tkt .= "cip=$clientip;"; $tkt .= "validuntil=$validuntil;"; if ( isset($graceperiod) && is_numeric($graceperiod) && $graceperiod > 0 ) { $tkt .= "graceperiod=".($validuntil-$graceperiod).";"; } $tkt .= "tokens=$tokens;udata=$udata"; if ($privkeytype == "DSA") $algoparam = "-dss1"; else $algoparam = "-sha1"; $fd = @proc_open(OPENSSL_PATH . " dgst $algoparam -binary -sign " . escapeshellarg($privkeyfile), array(0 => array("pipe", "r"), 1 => array("pipe", "w")), $pipes); if (!is_resource($fd)) { echo "Cannot start openssl"; return false; } fwrite($pipes[0], $tkt); fclose($pipes[0]); $sig = fread($pipes[1], 8192); fclose($pipes[1]); $res = proc_close($fd); if ($res != 0) { echo "openssl returned exit status $res"; return false; } return $tkt . ";sig=" . base64_encode($sig); } /* Validate a ticket. Parameters: pubkeyfile path to public key file (PEM format) pubkeytype type of public key ("RSA" or "DSA") ticket ticket string (including signature) Returns: ticket valid true/false */ function pubtkt_verify($pubkeyfile, $pubkeytype, $ticket) { /* strip off signature */ $sigpos = strpos($ticket, ";sig="); if ($sigpos === false) return false; /* no signature found */ $ticketdata = substr($ticket, 0, $sigpos); $sigdata = base64_decode(substr($ticket, $sigpos + 5)); if (!$sigdata) return false; /* write binary signature to temporary file */ $tmpfn = tempnam("/tmp", "tktsig"); $tmpfd = fopen($tmpfn, "wb"); fwrite($tmpfd, $sigdata); fclose($tmpfd); if ($pubkeytype == "DSA") $algoparam = "-dss1"; else $algoparam = "-sha1"; /* check DSA signature */ $fd = proc_open(OPENSSL_PATH . " dgst $algoparam -verify " . escapeshellarg($pubkeyfile) . " -signature " . escapeshellarg($tmpfn), array(0 => array("pipe", "r"), 1 => array("pipe", "w")), $pipes); fwrite($pipes[0], $ticketdata); fclose($pipes[0]); $res = trim(fgets($pipes[1])); fclose($pipes[1]); proc_close($fd); unlink($tmpfn); return ($res === "Verified OK"); } /* Parse a ticket into its key/value pairs and return them as an associative array for easier use. */ function pubtkt_parse($ticket) { $tkt = array(); $kvpairs = explode(";", $ticket); foreach ($kvpairs as $kvpair) { list($key,$val) = explode("=", $kvpair, 2); $tkt[$key] = $val; } return $tkt; } ?>
認証画面
/var/www/login.php
/var/www/login.php
<?php require_once('LdapAuth.php'); $invalid = false; $redirect = isset($_POST['_done'])?$_POST['_done']:(isset($_GET['_done'])?$_GET['_done']:'/index.html'); if(isset($_POST['userid'])&&isset($_POST['password'])){ $userId = $_POST['userid']; $password = $_POST['password']; $ldapAuth = new LdapAuth(); try{ $ticket = $ldapAuth->cert($userId, $password); setcookie('auth_pubtkt', $ticket, 0, '/', '.totto.local'); header('Location: '.$redirect); exit(0); }catch(LdapUserOrPasswordInvaildException $e){ $invalid = true; }catch(Exception $e){ error_log($e->getmessage()); echo "System error."; exit(1); } } ?> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <form name="form" method="post" action="./"> <?php echo $invalid?'Invalid UserId or Password.':'' ?><br /> <p>UserId:<input type="text" name="userid" /></p> <p>Password:<input type="password" name="password" /></p> <p><input type="submit" name="submit" /> <input type="hidden" name=".done" value="<?php echo $redirect; ?>" /> </form> </body> </html>
index.htmlなどがあった場合は削除する
Webサーバ構築
apacheモジュールをインストールする
$ wget https://neon1.net/mod_auth_pubtkt/mod_auth_pubtkt-0.6b.tar.gz $ sudo apt-get install apache2-threaded-dev $ tar xzfv mod_auth_pubtkt-0.6b.tar.gz $ cd mod_auth_pubtkt $ sudo ./configure $ sudo make $ sudo make install
公開鍵を持ってくる
$ cd $ scp login.totto.local:/etc/ldap-login-keypair/pubkey.pem . $ sudo mkdir -p /etc/ldap-login-keypair/ $ sudo mv pubkey.pem /etc/ldap-login-keypair/
apacheの設定をする
/etc/apache2/httpd.conf
/etc/apache2/httpd.conf
LoadModule auth_pubtkt_module /usr/lib/apache2/modules/mod_auth_pubtkt.so #AddModule mod_auth_pubtkt.c # Apache 1.3 only
/etc/apache2/sites-available/default
TKTAuthPublicKey /etc/ldap-login-keypair/pubkey.pem <Directory /var/www/auth> Order Allow,Deny Allow from all AuthType mod_auth_pubtkt TKTAuthLoginURL http://login.totto.local/ #TKTAuthTimeoutURL http://yahoo.co.jp/ #TKTAuthUnauthURL http://livedoor.com/ TKTAuthBackArgName .done #TKTAuthToken "myserver" require valid-user </Directory>
$ sudo /etc/init.d/apache2 restart
静的ページで確認をする
/var/www/auth/index.html
LoggedIn!
http://web.totto.local/auth/
にアクセスすると、ログインサーバにリダイレクトされる。
LDAPで設定されているユーザ・パスワードでログインすると、
元のページに戻り LoggedIn! が表示される。
にアクセスすると、ログインサーバにリダイレクトされる。
LDAPで設定されているユーザ・パスワードでログインすると、
元のページに戻り LoggedIn! が表示される。
PHPで確認する
$ sudo apt-get install php5
/var/www/auth/hoge.php
<?php echo 'REMOTE_USER:' . getenv('REMOTE_USER') . "\n"; echo 'REMOTE_USER_DATA:' . getenv('REMOTE_USER_DATA') . "\n"; ?>
以下のような感じで環境変数から情報を取得できる
REMOTE_USER:0001 REMOTE_USER_DATA:uid=0001,ou=unit1-1,ou=unit1,dc=example,dc=com
以下広告