Old revision
Revision as of 2023-08-07 03:46
---
parent: ../Security
title: Digest認証とセッション認証を組み合わせた認証の提案
date: 2019-11-23
tags: セッション認証, フォーム認証, ダイジェスト認証, Web, PHP
---
本稿では, Digest認証, およびセッション認証の欠点を互いに補うあう,
二つを組み合わせた認証方法を提案する.
まず, Digest認証とセッション認証について簡単に説明したのち, 本題に入る.
===
# Digest認証
Digest認証は, HTTPの認証の一つで, Webサーバはユーザのブラウザと
資格情報(ユーザ名, パスワードなど)をやり取りする^[digest-wiki].
Digest認証は, ユーザ名とパスワードをハッシュ化してから, ネットワークに流す.
一方, 別のHTTP認証であるBasic認証は, ハッシュ化の代わりに簡単に複合可能なBase64変換を使うため,
TLSと組み合わせない限りセキュア(安全)ではない.
Digest認証の一般的な手続き^[digest-wiki]は以下のとおりである.
1. クライアントは, 認証が必要なページにアクセスする.
2. Webサーバは, 401 "Unauthorized" ステータスとともに, \
`authentication realm`, ランダムな文字列`nonce`を返す.
3. (ブラウザにユーザ名とパスワードの情報が無いとき)\
ブラウザは, ユーザに`authentication realm`を提示し, ユーザ名とパスワードの入力を求める.\
ここで, ユーザはキャンセルできる.
4. クライアントは, レスポンスコードを含めた認証ヘッダを加えて同じリクエストをWebサーバに送る.
5. Webサーバは認証を認め, ページを返す. もしユーザ名, パスワードが異なる場合, \
サーバは401を返し, クライアントは, 再びユーザに入力を求める.

# 特徴
* 資格情報のやり取りでユーザ名とパスワードはハッシュ化される(平文ではない)
* 一度認証されると, ブラウザは自動で認証ヘッダを送信するため, ログアウト機能はない^[digest-logout]
# セッション認証
セッションベースの認証は, ユーザのログイン状態をサーバに保存する認証方法の一つである^[session-what].
セッションベースの認証では, ユーザがログインしたときにサーバがセッションデータを作成し保存する.
その次にセッションIDをユーザのブラウザにクッキーとして保存する.
そのあとの処理で, ブラウザがセッションIDをサーバに送り, サーバはそれとセッションデータを照らし合わせて,
ユーザのログイン状態を把握する.
セッション認証の一般的な手続き^[session-what]は以下のとおりである.
1. クライアントは, ユーザ名とパスワードを送信
2. サーバはセッションデータを作成し保存後, セッションIDをブラウザのクッキーに送信
3. クライアントはセッションIDを含めて認証が必要なページにアクセス
4. サーバはセッションIDとセッションデータを照らし合わせる
5. セッションデータからユーザがログイン状態であるとき, サーバはページを送信

# 特徴
* ログイン/ログアウト機能がある. ログアウトはセッションの破棄で可能
* ログイン後は, セッションIDでログイン状態を判定するので, \
クライアントは, アクセス権が必要なページごとに資格情報を送る必要なし.
* フォーム入力によりユーザ名・パスワードを送信するため, \
クライアント側でユーザ名とパスワードを暗号化していないときや, \
通信路が暗号化されていない(TLSではない)ときは安全ではない.
# 提案手法
本提案手法は, Digest認証とセッション認証の欠点を互いに補い合う方法である.
Digest認証およびセッション認証の利点と欠点は次のとおりである.
Digest認証:
✅利点:
* 資格情報のやり取りでユーザ名とパスワードはハッシュ化される(平文ではない)
❌欠点:
* ログアウト機能はない
* 認証が必要なページごとに資格情報を送る必要あり
セッション認証:
✅利点:
* ログイン/ログアウト機能 あり
* 認証が必要なページごとに資格情報を送る必要なし
❌欠点:
* 単純なフォーム認証の場合, ユーザ名・パスワードが平文でネットワークに流れる
二つの欠点を補いあい, 本提案手法の利点は次のとおりである.
提案手法:
✅利点:
* 資格情報のやり取りでユーザ名とパスワードはハッシュ化される(平文ではない)
* ログイン/ログアウト機能 あり
* 認証が必要なページごとに資格情報を送る必要なし
# 本手法が適している状況
本手法が向いていると思われる状況は,
* TLSを使用できない環境で, ある程度セキュリティを確保しつつユーザ管理を持ったウェブアプリを作りたい状況^[注.situ]
です.
フリーのレンタルサーバを使ってTLSを使用できない状況で
ちょっと凝ったウェブアプリ・ウェブシステムを作りたい方などに当てはまるかもしれません.
# 認証手続き
1. サーバはログインページを送信
2. ユーザがログインURLをクリック
3. サーバは, 401ステータスとともに有効期限付きのnonceとrealmを返す
4. ブラウザは, realmを表示し, ユーザにユーザ名とパスワードの入力を求めたのち, サーバに送信
5. サーバは, ユーザ名とパスワードを検証し, 成功したとき, nonceを破棄し, セッションデータを作成, \
セッションIDをブラウザのクッキーに送信
6. クライアントはセッションIDを含めて認証が必要なページにアクセス
7. サーバはセッションIDとセッションデータを照らし合わせる
8. セッションデータからユーザがログイン状態であるとき, サーバはページを送信

# digest認証でのログアウトについて
一般的に, digest認証では認証状態(ログイン・ログアウト状態)をブラウザ側が持っています^[digest-wiki]^[digest-logout].
なので, サーバ側がログアウトをブラウザに強制することはできません.
そこで, 先行手法では, 「ダミーのユーザ名で再認証を促す^[digest-logout-trad-method]」といった方法があります.
ですが, この方法では, 一部のブラウザ(firefoxなど)で警告が表示される場合があり, ユーザに不信感を与えかねません.
本提案方法では, //一度限りの認証のみ有効な制限時間付き//の`nonce`を用意することで対応させています.
本手法では, 認証が必要な時に制限時間付きの`nonce`を発行し, ブラウザに資格情報を送らせます.
ブラウザからの応答が遅く制限時間を超えると発行した`nonce`は無効化されます.
また, 制限時間内に資格情報を送り検証に成功した場合でも, 発行した`nonce`を無効化します.
一度限りの`nonce`のおかげで, ブラウザに再認証を促すことが可能です.
多くのブラウザでは, `nonce`を使いまわして再認証する手間を省きますが, 本手法ではそれができません.
ブラウザは認証ページで必ずサーバから401ステータスをもらって再びユーザにユーザ名とパスワードを入力してもらう必要があります^[注.logout-safari].
認証が必要なページごとに再認証が求められるわけではなく, 求められるのはログイン時のみです.
本手法では一度認証に成功(ログイン)すると, それ以降はdigest認証ではなくセッションベースの認証を行います.
ログアウトでは, セッションベースの認証と同じくセッションの破棄を行います.
# 実装
本手法「Digest認証とセッション認証を組み合わせた認証方法」は, 2020-8-20時点で
[ContentsPlanet](https://contentsviewer.work/Master/ContentsPlanet/ContentsPlanet)というコンテンツ管理システム(CMS)
で使用されています. ContentsPlanet は, TLSを利用できない環境を想定して作られています.
本手法が実装されたスクリプトは以下のようになっています.
`Authenticator.php`:
<https://github.com/ContentsViewer/ContentsPlanet/blob/master/Module/Authenticator.php>
* digest認証での検証処理
* セッションベース認証での検証処理
`CacheManager.php`:
<https://github.com/ContentsViewer/ContentsPlanet/blob/master/Module/CacheManager.php>
* 一時的に`nonce`を記録したりする
`index.php`:
<https://github.com/ContentsViewer/ContentsPlanet/blob/master/index.php>
* フロントページの窓口
* ここで, 認証が必要か確認する
* 再認証が必要な時は, `403ページ`へとぶ
`403.php`:
<https://github.com/ContentsViewer/ContentsPlanet/blob/master/Frontend/403.php>
* 再認証を促すページ
* ここに, ログイン画面へのリンクがある
`login.php`:
<https://github.com/ContentsViewer/ContentsPlanet/blob/master/Frontend/login.php>
* ログインページ
`logout.php`:
<https://github.com/ContentsViewer/ContentsPlanet/blob/master/Frontend/logout.php>
* ログアウトページ
* セッションの破棄を行う
# 注釈
[注.situ]: かなりニッチです
[注.logout-safari]: ブラウザであるsafariはそれでも, 再認証をパスします. あきらめずもう一度トライしているのでしょうか? \
ただブラウザを一度終了すると再認証を求められます.
# 参考文献
[digest-wiki]: "[Digest access authentication](https://en.wikipedia.org/wiki/Digest_access_authentication)". \
Wikipedia. (accessed at 2020-2-18)
[digest-logout]: "[ログアウト機能の目的と実現方法](https://blog.tokumaru.org/2013/02/purpose-and-implementation-of-the-logout-function.html)". \
徳丸浩の日記. (accessed at 2020-2-19)
[session-what]: "[What really is the difference between session and token based authentication](https://dev.to/thecodearcher/what-really-is-the-difference-between-session-and-token-based-authentication-2o39)". \
Brian Iyoha. (accessed at 2020-2-22)
[digest-logout-trad-method]: "[PHPによる簡単なログイン認証いろいろ](https://qiita.com/mpyw/items/bb8305ba196f5105be15)". \
Qitta. (accessed at 2020-3-1)