MySqlに保存している認証情報を用いてSimpleSAMLphpでSSOする




## Index

* [前提](#前提)
* [MySqlにユーザアカウントを作成](#mysqlにユーザアカウントを作成)
* [<font color="#660033"><b>IdP</b></font>の設定](#idpの設定)
* [<font color="#007c99"><b>SP</b></font>とSAML連携してみる](#spとsaml連携してみる)
* [SAMLResponseにパラメータを含ませる](#samlresponseにパラメータを含ませる)


## 前提

* ここではXAMPPを使っています、なのでデータベースはMySqlです。
* [こちらの記事](https://qiita.com/yamori4/items/1aac9a5137d63c0b0a61)の環境構築をした後に本記事を書いています。
* SimpleSAMLphpはすでにダウンロード済でApache制御下のフォルダに配置済という想定です。
* SimpleSAMLphpのフォルダ名を<font color="#660033"><b>ssoIdp</b></font>に変更して使用しており、以下のような構成になっています。

<span><font color="#808000">
　　C:\xampp\htdocs (←Apacheの制御下のフォルダ、環境の設定によって変わる)
　　　　　　　　└ <font color="#660033"><b>ssoIdp</b></font>(←SimpleSAMLphpのサイトからダウンロードして解凍した
　　　　　　　　　　　　　　フォルダ[simplesamlphp-1.19.0-rc1]をリネームしています。)
</font></span>

## MySqlにユーザアカウントを作成

* phpMyAdminのコンソール画面を開きます

XAMPPのコンソールからMySQLを「Start」した後、[Admin]ボタンをクリックしてphpMyAdminのコンソール画面を開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/74998bf3-731e-7063-0416-1a8c3240d7ad.png)

* データベースを作成します

phpMyAdminにログイン(IDとパスワードを設定している場合は認証してログイン)して、左端カラムの[新規作成]をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/ee0f2f3f-7f94-81bc-d414-31d639403451.png)

データベースの名前に「simplesaml」と入力し、[作成]ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/14aafb84-ec50-3b4e-a4e5-b6b132171174.png)


* テーブルを作成します

[このページ(sqlauth:sql)](https://simplesamlphp.org/docs/stable/sqlauth:sql)記載のSQLを実行すればよいです。
データベース"simplesaml"の"SQL"タブでCREATE文を入力し、[実行]ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/7a663981-cc89-cba3-0f45-8f56c3df6c1b.png)

データベース"simplesaml"に、"users"テーブルと"usergroups"テーブルが作成されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/6a0994d1-6da9-97cf-9c1f-6441a671bf78.png)


* PHPのプログラムからユーザを登録

"users"テーブルの"password"カラムの値は、『saltとpassword平文を結合した文字列にハッシュをかけたもの』です。なのでユーザの登録がちょっと面倒ですからPHPでさくっと登録用画面のプログラムを書きました、割と適当ですが。

```addUserToSimpleSaml.php
<?php
class simpleSamlDb
{
	//各人のMySqlの環境に合わせて設定してください。
    const DATA_SOURCE_NAME = "mysql:dbname=simplesaml;host=localhost";
    const DB_USER_NAME = "root";
    const DB_PASSWORD = null;

    private $dbh;

    function  __construct()
    {
        try {
            $this->dbh = new PDO(self::DATA_SOURCE_NAME, self::DB_USER_NAME, self::DB_PASSWORD);
        } catch (Exception $ex) {
            var_export($ex->getMessage());
        }
    }

    public function addUser($uid, $password, $givenname, $email)
    {

        $sql = "insert into users (uid, password, salt, givenname, email, eduPersonPrincipalName) "
            . "values (:uid, SHA2(:password, 256), :salt, :givenname, :email, :eduPersonPrincipalName)";

        try {
            $salt = substr(bin2hex(random_bytes(32)), 0, 32); //ランダム文字列を生成

            $this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
            $stmt = $this->dbh->prepare($sql);
            $stmt->bindValue(":uid", $uid, PDO::PARAM_STR);
            $stmt->bindValue(":password", $salt . $password, PDO::PARAM_STR);
            $stmt->bindValue(":salt", $salt, PDO::PARAM_STR);
            $stmt->bindValue(":givenname", $givenname, PDO::PARAM_STR);
            $stmt->bindValue(":email", $email, PDO::PARAM_STR);
            $stmt->bindValue(":eduPersonPrincipalName", $email, PDO::PARAM_STR); //とりあえずemailと同じ値にしてます。eduPersonの仕様にはそんなに詳しくないので。
            if($stmt->execute() == 1 ){
                echo "登録しました";
            };
        } catch (PDOException $ex) {
            echo "hopge";
            var_export($ex->getMessage());
        }
    }
}

if (
    array_key_exists("uid", $_POST) &&
    array_key_exists("password", $_POST) &&
    array_key_exists("givenname", $_POST) &&
    array_key_exists("email", $_POST)
) {
    $db = new simpleSamlDb();
    $db->addUser($_POST["uid"], $_POST["password"], $_POST["givenname"], $_POST["email"]);
}

?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>SimpleSAMLphpのDBにユーザを追加したりする</title>
</head>
<body>
    <form method="POST" action="#">
        <label>uid</label><br />
        <input id="uid" name="uid" value="testuser" /><br />
        <label>password</label><br />
        <input id="password" name="password" value="testpass" /><br />
        <label>givenname</label><br />
        <input id="givenname" name="givenname" value="testgivenname" /><br />
        <label>email</label><br />
        <input id="email" name="email" value="test@example.simplesaml.com" /><br />
        <input type="submit" id="submit" value="送信" />
    </form>
</body>
</html>
```

このPHPのソースコードをWebブラウザで表示するとこんな感じになります。入力欄に値を設定して[送信]ボタンをクリックすればMySqlにユーザが登録されます。
ここでは（ユーザ名：testuser、パスワード：testpass）で登録しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/abef7139-21d6-a966-bbbf-d6661a389e6b.png)

Webブラウザから登録したユーザをphpMyAdminのコンソールから確認しました。ちゃんと登録されてますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/db067c38-4cc2-39b3-dddc-1a644aceff42.png)


## <font color="#660033"><b>IdP</b></font>の設定

* 設定ファイル「<font color="#660033"><b>ssoIdp</b></font>\\config\\config.php」を編集します。

URLのパスを変更します。

```ssoIdp\config\config.php
'baseurlpath' => 'simplesaml/',
             ↓↓↓
'baseurlpath' => 'ssoIdp/', 
```

アドミニストレータのパスワードも変更します。（初期設定の値から変更しておかないとログインできないので。）

```ssoIdp\config\config.php
'auth.adminpassword' => '123',
             ↓↓↓
'auth.adminpassword' => '123456',
```

<font color="#660033"><b>IdP</b></font>でのsaml2.0の機能を有効化します。

```ssoIdp\config\config.php
'enable.saml20-idp' => false,
             ↓↓↓
'enable.saml20-idp' => true,
```

sqlauthを有効化します。

「 <font color="#660033"><b>ssoIdp</b></font>\\config\\config.php 」のmocule.enableに「'sqlauth' => true」を追加します。

```ssoIdp\config\config.php
     'module.enable' => [
         'exampleauth' => false,
         'sqlauth' => true,
         'core' => true,
         'saml' => true
     ],
```




* <font color="#660033"><b>ssoIdp</b></font>/config/authsources.phpの設定

MySqlに接続して、認証情報取得のために実行するSQLクエリを設定を追加します。（※'dsn','username','password'は各自の環境に合わせて設定してください。 ）

```config/authsources.php
    /*
    'example-sql' => [
        'sqlauth:SQL',
        'dsn' => 'pgsql:host=sql.example.org;port=5432;dbname=simplesaml',
        'username' => 'simplesaml',
        'password' => 'secretpassword',
        'query' => 'SELECT uid, givenName, email, eduPersonPrincipalName FROM users WHERE uid = :username ' .
            'AND password = SHA2(CONCAT((SELECT salt FROM users WHERE uid = :username), :password), 256);',
    ],
    */

          ↓↓↓

    'example-sql' => [
        'sqlauth:SQL',
        'dsn' => 'mysql:host=localhost;port=3306;dbname=simplesaml',
        'username' => 'root',
        'password' => '',
        'query' => 'SELECT uid, givenName, email, eduPersonPrincipalName FROM users WHERE uid = :username ' .
            'AND password = SHA2(CONCAT((SELECT salt FROM users WHERE uid = :username), :password), 256);'
    ]
```


* <font color="#660033"><b>IdP</b></font>サーバのメタデータを編集します

使用する認証ソースを'example-userpass'から'example-sql'に変更します。

```ssoIdp\metadata\saml20-idp-hosted.php
    /*
     * Authentication source to use. Must be one that is configured in
     * 'config/authsources.php'.
     */
    'auth' => 'example-userpass',
          ↓↓↓
    'auth' => 'example-sql',
```

* 鍵ペアを <font color="#660033"><b>ssoIdp</b></font>\cert 配下に配置します。

opensslなどを使って鍵ペアを生成すればいいです。
一応、[こちらのページ](https://qiita.com/yamori4/items/1aac9a5137d63c0b0a61)に<b>サンプルの鍵ペア（秘密鍵をを公開しているので実用には耐えない）</b>の中身を用意してあるので、そちらを使ってもらってもかまいません。
こんな感じのファイル構成になります。

```
C:\xampp\htdocs (←Apacheの制御下のフォルダ、環境の設定によって変わる)
　　　　　　└ ssoIdp
   　　　　　　└ cert
      　　　　　　├ server.pem
      　　　　　　└ server.crt
```

【補足】
<font color="#660033"><b>ssoIdp</b></font>\\metadata\\saml20-<font color="#660033"><b>idp</b></font>-hosted.php にて以下のように秘密鍵と証明書の設定をしているのですが、秘密鍵と証明書のファイル名を同じにしているので、こちらの設定を変更する必要はないです。

```ssoIdp\metadata\saml20-idp-hosted.php
    // X.509 key and certificate. Relative to the cert directory.
    'privatekey' => 'server.pem',
    'certificate' => 'server.crt',
```


* Apacheの設定

&lt;Directory "C:/xampp/htdocs"&gt;の下あたりにでも以下のように設定を追加します。

```\xampp\apache\conf\httpd.conf
DocumentRoot "C:/xampp/htdocs"
<Directory "C:/xampp/htdocs">
   ～省略～
</Directory>

<Directory "C:\xampp\htdocs\ssoIdp\www">
    Allow from localhost
</Directory>
<Directory "C:\xampp\htdocs\ssoIdp">
    Deny from all
</Directory>

Alias /ssoIdp/ "C:/xampp/htdocs/ssoIdp/www/"
```

* MySqlに登録したユーザアカウントを用いて認証テストをしてみます

WEBブラウザで表示した設定画面の[認証]タブの「設定されている認証元をテスト」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/fa8ffb6d-1392-b5e0-c108-84121b022f99.png)

先程設定した example-sql を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/0b4cba94-0edc-dac3-c769-67e208bf3af1.png)

先程MySqlに追加したユーザアカウントのIDとパスワード(ユーザ名：testuser、 パスワード : testpass)を入力し、[ログイン]をクリックしてください。ちなみにすでに<font color="#660033"><b>IdP</b></font>での認証が済んでいる場合は、この画面はスキップされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/354dd498-653e-ddb7-aa16-39955d7f2894.png)

認証に成功すると以下のような画面が表示されます。"属性"の項を見てみると、config.phpで設定したSQL (*SELECT uid, givenName, email, eduPersonPrincipalName FROM users ....*)によりMySqlからパラメータを取得できていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/68a96e49-a9ef-a527-54ff-f9ad2bbd8932.png)


## <font color="#007c99"><b>SP</b></font>とSAML連携してみる

※<font color="#007c99"><b>SP</b></font>サーバの構築の仕方については[こちら](https://qiita.com/yamori4/private/1aac9a5137d63c0b0a61)を参考にしてください。

* <font color="#007c99"><b>SP</b></font>サーバの設定画面＞[認証]タブ＞"設定されている認証元をテスト"＞"example-sql"＞プルダウンで<font color="#660033"><b>IdP</b></font>を[選択]して、<font color="#007c99"><b>SP</b></font>からSAMLRequestを<font color="#660033"><b>IdP</b></font>に飛ばす。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/0c67d765-7377-cc18-05bd-978cb09655f6.png)

* <font color="#660033"><b>IdP</b></font>サーバの認証画面へ遷移するので、先程MySqlに追加したユーザアカウントのIDとパスワード(ユーザ名：testuser、 パスワード : testpass)を入力し、[ログイン]をクリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/354dd498-653e-ddb7-aa16-39955d7f2894.png)

* <font color="#660033"><b>IdP</b></font>での認証に成功すると、<font color="#660033"><b>IdP</b></font>から<b>SAMLResponse</b>が<font color="#007c99"><b>SP</b></font>に送信され、それの検証により<font color="#007c99"><b>SP</b></font>での認証が完了します。下の画像は<font color="#007c99"><b>SP</b></font>での認証に成功したときのものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/1ac9266a-8657-0863-9e0e-2c5e3ad7966c.png)

## SAMLResponseにパラメータを含ませる

先に書いちゃいますけど、SAMLの仕様により<b>SAMLResponse</b>内の<b>AttributeStatement要素</b>に諸々の属性を追加できます。
下の例だと、上から順に"uid","givenName","email","eduPersonPrincipalName","groups"のパラメータが含まれています。
"uid"の値によって<font color="#007c99"><b>SP</b></font>側で利用ユーザを特定できるのは当然として、"groups"はユーザが属しているグループを意味していますから、それによって<font color="#007c99"><b>SP</b></font>側で利用可能な機能や権限を管理するといった使い方もできます。この例のユーザだと<b>pop</b>と<b>rock</b>という2つのグループに属しています。

```xml
<saml:AttributeStatement>
  <saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
    <saml:AttributeValue xsi:type="xs:string">testuser</saml:AttributeValue>
  </saml:Attribute>
  <saml:Attribute Name="givenName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
    <saml:AttributeValue xsi:type="xs:string">testgivenname</saml:AttributeValue>
  </saml:Attribute>
  <saml:Attribute Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
    <saml:AttributeValue xsi:type="xs:string">test@example.simplesaml.com</saml:AttributeValue>
  </saml:Attribute>
  <saml:Attribute Name="eduPersonPrincipalName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
    <saml:AttributeValue xsi:type="xs:string">test@example.simplesaml.com</saml:AttributeValue>
  </saml:Attribute>
  <saml:Attribute Name="groups" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
    <saml:AttributeValue xsi:type="xs:string">pop</saml:AttributeValue>
    <saml:AttributeValue xsi:type="xs:string">rock</saml:AttributeValue>
  </saml:Attribute>
</saml:AttributeStatement>
```

* では、ユーザとグループを紐づけてみましょう。phpMyAdminのコンソールから"usergroups"テーブルに"testuser"に紐づくグループのデータを挿入します。ここでは<b>rock</b>と<b>pop</b>というグループに"testuser"を紐づけています。値を入力して[実行]ボタンをクリックすればいいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/8c71265b-3034-f63f-f31e-7bf109d90494.png)

ちなみにSQLだとこのようなクエリになります。

```sql
INSERT INTO `usergroups` (`uid`, `groupname`) VALUES ('testuser', 'rock'), ('testuser', 'pop');
```

* データが挿入されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/632c1336-22d1-ae16-3d3d-864b2957dd49.png)


* <font color="#660033"><b>IdP</b></font>の <font color="#660033"><b>ssoIdp</b></font>/config/authsources.php で設定している、<b>MySqlから認証情報を取得するために実行するSQLクエリ</b>を変更します。

```config/authsources.php
    'example-sql' => [
        'sqlauth:SQL',
        'dsn' => 'mysql:host=localhost;port=3306;dbname=simplesaml',
        'username' => 'root',
        'password' => '',
        'query' => 'SELECT users.uid, givenName, email, eduPersonPrincipalName, groupname AS groups ' .
            'FROM users LEFT JOIN usergroups ON users.uid = usergroups.uid ' .
            'WHERE users.uid = :username AND password = SHA2(CONCAT((SELECT salt FROM users WHERE uid = :username), :password), 256);'
    ],
```

[このページ(sqlauth:sql)](https://simplesamlphp.org/docs/stable/sqlauth:sql)記載のSQLを少し変更したものです、"usergroups"テーブルをJOINしてSELECTするSQLに変更してます。
（このSQLを実行すると複数行を返しますが、その場合は属性単位で纏める仕様らしいです。）

* 以下は実際にSAML連携をしてみたときの<font color="#007c99"><b>SP</b></font>の画面です。<b>groups</b>の情報が<font color="#660033"><b>IdP</b></font>から<font color="#007c99"><b>SP</b></font>に伝えられてますね。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689761/1a117a4b-97c3-9151-6aed-eed2f296464d.png)


<b>【備考】</b>
このような<font color="#660033"><b>IdP</b></font>から受け取ったパラメータを<font color="#007c99"><b>SP</b></font>で利用することにより、<font color="#808000">Just-In-Time(JIT)プロビジョニング連携</font>をすることもできます。

<b>JIT連携とは</b>‥‥一般的なSSOサービスを利用する場合、事前に同じユーザ情報を<font color="#660033"><b>IdP</b></font>と<font color="#007c99"><b>SP</b></font>の両方で登録しておく必要があります。しかし、<font color="#660033"><b>IdP</b></font>からのSAMLResponseを<font color="#007c99"><b>SP</b></font>が受け取ったタイミング(Just-In-Time)でユーザを新規登録することにより、<font color="#808000">事前のユーザ登録をすることなくSSOを利用できる仕組み</font>です。

### 以上