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 <[email protected]>
*/
/* 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
以下広告