ユーザ認証とアクセス権の設定をサポートします。INTER-Mediator自身がデータベースを利用して、ユーザーやグループを管理する手法を「ビルトイン認証」、データベースエンジン側に登録したユーザーを利用する認証を「ネイティブ認証」、LDAPサーバのユーザーを利用した認証を「LDAP認証」と呼びます。また、OAuth2(ただし、Googleのみ確認)やSAMLにも対応しています。INTER-Mediator Ver.8では、パスワード記録時のハッシュを従来のSHA-1から、5000ストレッチしたSHA-256にアップデートしています。従来のSHA-1でのハッシュと相互運用もできます。
認証動作に必要なテーブル
チャレンジ等を記録するテーブル
ユーザ認証ではセキュリティを高めるために、サーバーからクライアントに「チャレンジ」を送り、その値を利用して認証情報を作ります。そのため、サーバー側にチャレンジを残しておく必要があり、テーブルの定義が必要です。そのテーブルは、IM_Entryの第3引数あるいはparams.phpで指定したユーザで読み書きの権限が必要です。チャレンジのテーブル名は、IM_Entryの第2引数で指定するか、あるいは既定値の名前(issuedhash)のテーブルを作成します。テーブルには以下のフィールドが必要です。
フィールド名 | 型の例 | 説明 |
---|---|---|
user_id | INT, | ユーザテーブルのキーフィールドとなるid値(ユーザをデータベース内のテーブルで管理する場合に利用) |
username | VARCHAR(48), | ユーザテーブルのキーフィールドとなるid値(ユーザをデータベースエンジンが提供するもので運用する場合に利用) |
clienthost | VARCHAR(64), | クライアントを識別する情報。サーバーからランダムに与えられる40バイトのHEXコード。ただし、パスワードのセルフサービスのリセット時にSHA-256のハッシュ値のHEX文字列が入る。 |
hash | VARCHAR(64), | チャレンジに使うハッシュ値。実際には48バイトの16進数文字列。ただし、パスワードのセルフサービスのリセット時にSHA-256のハッシュ値のHEX文字列が入る。 |
expired | DateTime, | チャレンジの有効期限を示すタイムスタンプ値 |
なお、このテーブルへの検索では、user_idとclienthostあるいはusernameとclienthostの2つのフィールドに対してAND演算での検索条件が与えられます。従って、高速化するためには、この2つのフィールドを合成したインデックスを作っておきます。
ユーザーのテーブル
認証のためのユーザは、データベース内にテーブルを作成する方法と、データベースエンジンが管理するユーザによる方法を利用できます。データベース内にテーブルを作る場合、そのテーブルは、IM_Entryの第3引数あるいはparams.phpで指定したユーザで少なくとも読み出しの権限が必要です。チャレンジのテーブル名は、IM_Entryの第2引数で指定するか、あるいは既定値の名前(authuser)のテーブルを作成します。テーブルには以下のフィールドが必要です。
フィールド名 | 型の例 | 説明 |
---|---|---|
id | INT AUTO_INCREMENT, | 連番の数値を入れて、キーフィールドとする |
username | VARCHAR(48) | ユーザ名(特に制限はないが、重複した名前が定義されないようにするのが望ましい) |
hashedpasswd | VARCHAR(72) | パスワードのハッシュ値。SHA-256のハッシュ値と、4バイトのソルトを合成したコードのHEXが入るので、72バイト必要。 |
VARCHAR(48), | 電子メールアドレス。ユーザー名の代わりに使用したり、パスワードのリセットで利用 | |
limitdt | DateTime | LDAP認証ユーザーのキャッシュの期限 |
ユーザの場合は、ユーザ名をキーフィールドに使えるとも考えられますが、大量のユーザになったときなどに処理速度を有利にするために、内部的にはidフィールドによる連番で与えた数値で処理をすることにします。なお、usernameを条件として検索をかけるので、高速化のためにはこのフィールドにインデックスを作成しておきます。パスワードはハッシュを保存しますが、ハッシュ値は、次の手順で計算をします。
- パスワード(pw)と、4バイトのソルト(s)を用意します。
- パスワードとソルトをつなげた文字列のSHA-256を5000回のストレッチで求めます( sha256(pw+s, 5000) → H1)
- H1の16進数表現した文字列と、ソルトを16進数表現した文字列をつなげます(hex(H1)+hex(s)→H2)
- H2をフィールドに保存します。
コマンドで求める例としては、あまり効率良くはないですが、INTER-Mediatorのレポジトリにあるdist-docs/passwdgen.shスクリプトを利用できます。-uはユーザ名、-pはパスワード、-sはSQL文として出力するパラメータです。opensslのコマンドを内部で使っていますが、5000回のストレッチをコマンドから行う方法がわからず、シェルスクリプトで繰り返してopensslコマンドを動かしているため、ハッシュの計算に30秒から1分くらいかかると思われます。
% dist-docs/passwdgen.sh -u test -p TEST -s
INSERT authuser(username,initialpass,hashedpasswd) VALUES('test','TEST','5d4b09daced104e42bc5cfc1d4db6c677afd3ffeadc950a2873b009aeba39bab45654d4b');
なお、過去に開発したアプリケーションとの互換性を考慮して、SHA-1での利用も可能です。SHA-1ハッシュ値は、次の手順で計算をします。
- パスワード(pw)と、4バイトのソルト(s)を用意します。
- パスワードとソルトをつなげた文字列のSHA-1求めます( sha1(pw+s) → H1)
- H1の16進数表現した文字列と、ソルトを16進数表現した文字列をつなげます(hex(H1)+hex(s)→H2)
- H2をフィールドに保存します。
SHA-256互換ハッシュという、SHA-1からパスワードを変更せずにハッシュそのものはSHA-256に移行できるモードも用意しています。設定を行えば、SHA-1での認証が成功した時に、データベースのhashedpasswdフィールドの内容をこの互換ハッシュで書き換えます。次回以降の認証は、互換ハッシュを使っての認証となるので、パスワードを変更せずにSHA-256を使ったハッシュでの運用に移行できます。互換ハッシュは次の手順で計算をします。
- SHA-1で求めたHEX表現のハッシュをHとします。
- Hのソルトを求めてsとします(salt(H) → s)
- Hとsをつなげた文字列のSHA-256を5000回のストレッチで求めます( sha256(H+s, 5000) → H1)
- Hの16進数表現した文字列と、ソルトを16進数表現した文字列をつなげます。このH2がSHA-256互換ハッシュです(hex(H1)+hex(s)→H2)
グループを管理するテーブル
グループそのものを管理するテーブルと、ユーザーやグループの特定のグループへの所属を記録するテーブルの2つがあります。
フィールド名 | 型の例 | 説明 |
---|---|---|
id | INT AUTO_INCREMENT, | グループを識別するための番号 |
groupname | VARCHAR(48), | グループ名 |
フィールド名 | 型の例 | 説明 |
---|---|---|
user_id | INT, | 所属するユーザのidフィールドの値 |
group_id | INT, | 所属するグループのidフィールドの値 |
dest_group_id | INT, | 所属されるグループのidフィールドの値 |
アクセス権に関する設定の評価
認証の設定は基本的にコンテキストに行いますが、IM_Entryの2番目の引数にも行えます。記述可能な内容については、『定義ファイルでの記述方法a>』を参照してください。つまり、2カ所の設定があるので、以下のように、コンテキストに設定する方を「コンテキストの認証設定」、2つ目の引数に設定するのを「オプションの認証設定」とします。また、いずれも、アクセス権の設定も行いますが、「認証とアクセス権」を合わせてここでは「認証」というネーミングをします。
IM_Entry(
array(
array(
"name"=>"context",
"authentication"=>array(
/* コンテキストの認証設定 */
),
),
),
array(
"authentication"=>array(
/* オプションの認証設定 */
),
...);
オプションの認証設定に設定可能な情報
配列指定 | 値 | params.phpで変数名 | |
---|---|---|---|
第1次元 | 第2次元 | ||
'authentication' | 'user' | 利用可能なユーザを配列で指定。この記述がなければすべてのユーザにアクセス権。データベースのネイティブユーザで認証する場合には、値を「array('database_native')」と指定する。 | |
'group' | 利用可能なグループを配列で指定。この記述がなければすべてのグループにアクセス権 | ||
'user-table' | ユーザ情報が保存されているテーブル名。省略すると'authuser' | ||
'group-table' | ユーザ情報が保存されているテーブル名。省略すると'authgroup' | ||
'corresponding-table' | ユーザとグループが対応づけられている情報が保存されているテーブル名。省略すると'authcor' | ||
'challenge-table' | チャレンジが保存されているテーブル名。省略すると'issuedhash' | ||
'authexpired' | 認証が自動的に継続される時間を秒数で指定する。省略すると'3600'(1時間)。バージョン4.4以降では、0を指定すると有効期限はWebブラウザー終了時まで | ||
'storing' | 認証情報のクライアントへの保存を指定。'cookie'ならPathを指定しないでクッキーに保存(既定値)、'cookie-domainwide'ならドメイン全体で共有できるようにクッキーに保存、'session-storage'ならブラウザのセッションストレージに保存、'none'ならクッキーに保存しない。'credential'であれば認証情報をhttp-onlyのクッキーに保存する | ||
'realm' | 認証領域名。ログインパネルの上部に表示される。また、認証情報を記憶するクッキーの名称の末尾に付与される。 | ||
'email-as-username' | trueを指定すると、authuserテーブルのusernameと同時にemailフィールドも検索する。つまり、電子メールアドレスでの認証が可能になる。既定値はfalse | $emailAsAliasOfUserName | |
'issuedhash-dsn' | issuedhashテーブルに対するDSNを指定する。たとえば、FileMakerデータベースで、issuedhashテーブルをSQLiteで運用する場合に使える | $issuedHashDSN | |
'password-policy' | パスワード変更時に適用されるパスワードポリシー。useAlphabet useNumber useUpper useLower usePunctuation length(10) notUserNameの各単語をスペースで区切って指定する。useAlphabetはアルファベットを使用していないといけなくする。その他、同様に単語から意味が分かるはずである。length(10)はパスワードは10文字以上にする必要があるという意味で、( ) 内には任意の数値を記述できる。 | $passwordPolicy | |
'reset-page' | ログインパネルに、パスワードリセットページへのボタンを設置する。ここに、パスワードリセットページのURLを記載する。 | $resetPage | 'enroll-page' | ログインパネルに、ユーザー登録ページへのボタンを設置する。ここに、ユーザー登録ページのURLを記載する。 | $enrollPage | 'is-saml' | SAML認証を有効にする。なお、params.phpで、$samlAuthSourceの設定は必要。 | $isSAML | 'saml-builtin-auth' | SAML認証有効時、ビルトイン認証も有効にするかどうかを指定する。 | $samlWithBuiltInAuth |
'media-context' | メディアアクセスに認証を設定するとき、ここで指定したコンテキストにアクセスしたときにメディアの認証用のトークンをクライアントに届ける。このトークンをクライアントが持つ事で、メディアプロキシが認証される(Ver.6ではこの指定は不要になった) |
コンテキストの認証設定に設定可能な情報
配列指定 | 値 | ||
---|---|---|---|
キー | インデックス | 下位のキー | |
'authentication' | 'media-handling' | メディアアクセス時の認証を通すためのクッキー情報を、このコンテキストの読み出し時にクライアントに送り込む。必要なときに値にtrueを指定する | |
'all' | 'user' | すべての操作について、このコンテキストを利用可能なユーザを配列で指定。この記述がなければすべての認証ユーザにアクセス権 | |
'group' | すべての操作について、このコンテキストを利用可能なグループを配列で指定。この記述がなければすべてのグループにアクセス権 | ||
'target' | 省略あるいは'table'なら、テーブル全体に対する権限の設定、'field-user'あるいは'field-group'ならレコードごとに権限を指定できる | ||
'field' | 'target'に'field-user'あるいは'field-group'に指定した場合、ユーザ名やグループ名を指定するフィールドのフィールド名 | ||
'noset' | 'noset' => trueで指定すると、レコード作成時に、fieldで指定したフィールドにユーザ名あるいはグループ名を設定する操作を行わない。例えば、認証に使うフィールドを意図的にリレーションシップにより自動的に設定するようなことを意図した場合、リレーションシップのためのフィールド設定と、fieldに指定したフィールドへの入力が重複してSQLエラーが出る。それを回避するための設定。 | ||
'read' | (allと同様) | クエリーの操作について、このコンテキストに関する設定を行う | |
'update' | (allと同様) | 更新の操作について、このコンテキストに関する設定を行う | |
'create' | (allと同様) | 新規レコード作成の操作について、このコンテキストに関する設定を行う | |
'delete' | (allと同様) | レコード削除の操作について、このコンテキストに関する設定を行う | |
'protect-writing' | この配列に指定したフィールド名に対する更新処理を禁止する | ||
'protect-reading' | この配列に指定したフィールド名から読み出したデータをクライアントに送る事を禁止する |
コンテキストの認証設定なし、オプションの認証設定あり
タイトル用な条件だと、コンテキストの認証設定のuserとgroupがアクセス権の設定に関係します。もし、userもgroupも設定されていない場合は、認証が通ったユーザは何でもできると考えてください。
userあるいはgroupが設定されている場合、認証が通ると同時に、そのユーザあるいはそのユーザを含むグループが、userあるいはgroupの設定に含まれている必要があります。
コンテキストの認証設定あり、オプションの認証設定なし
オプションの設定のうちuserとgroupの設定がないということです。この場合、特定のコンテキストのデータベース処理に対して、コンテキストの認証設定が適用されます。
コンテキストの認証設定あり、オプションの認証設定あり
両方の認証設定があれば、先にオプションの認証設定が評価されて、その後にコンテキストの認証設定が評価されます。
コンテキストの認証設定の適用対象
適用対象として、4つのデータベース処理、read/create/update/deleteに対する処理と、それらすべてに対してまとめて設定を行うallがあります。
コンテキストの認証設定のtableとfleld
"table"キーに対する値がfield-userあるいはfield-groupの場合は、そのテーブルにあるfieldに指定したフィールドに、ログインが成功したユーザあるいはそのユーザが含むグループの名前が設定されちているレコードだけに絞り込まれます。検索条件にANDでユーザないしはグループの値をfieldに指定したフィールドに対する条件を付け加えます。したがって、fieldで指定したフィールドのうち、空欄ものは認証すればいっさいアクセスできないということになります。
"table"キーに対する値がfield-useやfield-groupではない場合、アクセス権はすべてのテーブルに渡って適用されます。レコードごとのアクセス権を記録するフィールドは不要です。このとき、userあるいはgroup属性に含まれていないユーザで認証した場合、いっさいのアクセスはできないようになっています。オプションの認証設定と異なる動作ですので、注意してください。
read/update/deleteに関しては、アクセス権は「検索条件」として集約されます。一方、createの操作に対しては、アクセス権がある場合だけ、レコードの作成が可能です。レコードの作成により、fieldで指定したフィールドに、ユーザないしはグループが設定されます。ユーザの場合は1つに決まりますが、グループの場合は、グループ名を並べ替えたときのいちばん先頭のグループ名を記録するようにします。
params.phpファイルへの指定
変数名 | 既定値 | 指定内容 |
---|---|---|
$passwordHash | "1" | "1"なら、SHA-1、SHA-256互換、SHA-256のいずれも対応。"2m"なら、SHA-256互換、SHA-256のいずれも対応。"2"なら、SHA-256のみ対応。 |
$alwaysGenSHA2 | false | $passwordHashが"1"で、この変数がfalseの場合、パスワード変更するとSHA-1でハッシュする。この変数がtrueなら、パスワード変更時にSHA-256でハッシュする。$passwordHashが"1"以外の場合は、この変数に関係なく、パスワードを変更すると、SHA-256でハッシュをかける。 |
$migrateSHA1to2 | false | trueにすれば、SHA-1でハッシュされたパスワードでログインをした後、そのハッシュ値をSHA-256互換のハッシュにコンバートする。実質的に、SHA-1のパスワードを変更しないでSHA-256に移行できる。 |
$resetPage | なし | URLを文字列で指定すると、ログインパネルに「パスワードをリセット」のボタンが表示され、クリックするとそのURLへジャンプする。ボタン名のメッセージの番号は2023 |
$enrollPage | なし | URLを文字列で指定すると、ログインパネルに「ユーザー登録をする(要メールアドレス)」のボタンが表示され、クリックするとそのURLへジャンプする。ボタン名のメッセージの番号は2022 |
$ldapServer | なし | LDAPサーバーのURLで、ldap://あるいはldaps://から始まる。この設定を行うと、LDAP認証が有効になる。 |
$ldapPort | なし | LDAPサーバーのポート番号。通常は389 |
$ldapBase | なし | ディレクトリ内のルートへのDNなど。$ldapContainerを繋げてユーザレコードが存在する場所を示す。 |
$ldapContainer | なし | $ldapBaseに繋げてユーザレコードが存在する場所を示す。 |
$ldapAccountKey | なし | ユーザー名として識別するデータがある属性名 |
$ldapExpiringSeconds | 1800 | ユーザー情報をビルトインユーザとしてキャッシュするが、キャッシュの有効期間。この期間を過ぎると、再度LDAPサーバへ認証を確認する。 |
$oAuthProvider | なし | OAuth認証時のプロバイダを指定する文字列で、"Google"のみ実装。 |
$oAuthClientID | プロバイダから供給されるクライアントID | |
$oAuthClientSecret | プロバイダから供給されるシークレット | |
$oAuthRedirect | 認証後にリダイレクトされるURL | |
$isSAML | false | SAML認証を有効にする。なお、SAMLのSPになるための設定については、INTER-Mediatorのレポジトリにあるsample/saml-configディレクトリの内容と説明を参照してください。 |
$samlAuthSource | なし | SPの識別名 |
$samlWithBuiltInAuth | false | trueにすると、SAML認証だけでなく、ビルトイン認証も並行して利用できる。そのため、INTER-Mediatorのログインパネルが表示され、そこに「SAML認証」ボタン(ボタン名のメッセージの番号は2026)が表示される |
ログインパネルのカスタマイズ
ログインパネルのカスタマイズについて、マイナーなものであれば、CSSの設定で行けると思います。ブラウザのデバッガで見ていただければ、ログインパネルの各要素はそれぞれclass属性が割り当てられているので、たとえば、ページのヘッダや独立したファイルでCSSを適用します。これらは、原則としてINTER-Mediatorのテーマ設定よりも優先度は高くなるので、上書きや変更は可能です。なお、ログインパネルのスタイル情報はテーマに含まれているので、テーマを作り直すという方法もあります。
ログインパネルを完全にカスタマイズしたいとう場合は、例えば、以下のようなプログラムを、INTERMediatorOnPage.doBeforeConstructに代入する関数の中などで実行します。最初の3行はプロパティ名から動作は明白と思われますが、ポイントは、ページに上乗せするログインパネル部分を、HTMLの文字列として、loginPanelHTMLプロパティに書き込みます。
INTERMediatorOnPage.isShowChangePassword = false;
INTERMediatorOnPage.defaultBackgroundImage = null;
INTERMediatorOnPage.defaultBackgroundColor = "white";
INTERMediatorOnPage.loginPanelHTML = '<div>....';
このloginPanelHTMLに書き込むHTMLはボディの一部となるような書き方をします。原則自由に記述できますが、この中にユーザー名を入れるテキストフィールドや、認証ボタンなどを配置する必要があります。それらは、決められたid属性を持つ必要があります。INTER-Mediatorが認識するid属性は、_im_password、_im_username、_im_authbutton、_im_changebutton、_im_oauthbuttonです。最初の2つがテキストフィールド、残りがボタンを想定しています。このid属性値を設定しておけば、例えば、認証ボタンとしての機能はINTER-Mediatorが自動的に組み込みます。
ネイティブ認証、LDAP認証で使う鍵データ
ネイティブ認証やLDAP認証では、クライアントで入力したパスワードを、サーバー側でも利用します。そのため、クライアントからサーバーにパスワードそのものを伝達しなければなりませんが、平文で流すことがないように、暗号化してクライアントからサーバーに送ります。そのために、RSAの鍵ペアを生成してサーバーに記録します。クライアントへは公開鍵だけが送られて、暗号にしたデータをサーバーに送ります。鍵ペアは例えば、「openssl rsagen -out gen.key 2048」のようなコマンドを入れてファイルをまず作ります。そして、その内容のテキストを、$params.phpファイルの変数$generatedPrivateKeyに代入します。INTER-Mediatorに含まれているparams.phpファイルで、該当する変数を探して、生成した鍵に置き換えてください。もし、鍵にパスワードが必要な場合には変数$passPhraseに記述できますが、このファイル自体はPHPが稼働していればクライアントから参照できないとはいえ、扱いには注意しましょう。
LDAP認証
LDAPサーバに関する情報を、params.phpファイルに記述をします。以下はその例です。変数ldapServerはLDAPサーバのURL、ldapPortはポート番号です。そして、ユーザーのDN(Distinguished Name)を決定するために、3つの変数を指定します。たとえば、ユーザー名がmsykなら、Open Directoryの場合はDNは「uid=msyk,cn=users,dc=homeserver,dc=msyk,dc=net」となります。「msyk」の前はldapAccountKey、後はldapContainerとldapBaseで指定した記述がつなげられて、DNを構成しています。ldapExpiringSecondsは、1回のLDAP認証後、指定した時間はLDAPサーバではなく、ネイティブ認証のためのテーブルにキャッシュした情報で認証します。これはパフォーマンスを高めるためです。最後に認証してから、変数ldapExpiringSeconds以上の時間が経過すると、改めてLDAPサーバに接続して認証を行います。
$ldapServer = "ldap://homeserver.msyk.net";
$ldapPort = 389;
$ldapBase = "dc=homeserver,dc=msyk,dc=net";
$ldapContainer = "cn=users";
$ldapAccountKey = "uid";
$ldapExpiringSeconds = 1800;