本記事は以下の4つの認証について解説します。
初めに
本記事は Web サーバー構築の第5回「Basic/Digest/Form/Bearer 認証」編です。
- 【Web サーバー構築1】Web/リバースプロキシ/ロードバランサー (nignx)
- 【Web サーバー構築2】プロキシ/リバースプロキシサーバー (squid)
- 【Web サーバー構築3】アプリケーションサーバー (gunicorn + Flask)
- 【Web サーバー構築4】HTTPS に対応 (Let’s Encrypt)
- 【Web サーバー構築5】Basic/Digest/Form/Bearer 認証 ←イマココ
- 【Web サーバー構築6】REST API サーバー(Flask)
認証の種類
Web サービスの認証の種類には、主に以下の4つが存在します。
Basic 認証 | Digest 認証 | フォーム認証 | Bearer 認証 | |
---|---|---|---|---|
パスワードを暗号化して送信 (HTTPS でカバー可能) | × | ○ | × | ○ (暗号化可能) |
パスワードを都度送信しない | × | × | ○ | ○ |
ログインせずにアクセス | × | × | ○ | △ (匿名) |
ログアウト (ログインを無効) | × | × | ○ | ○ |
シングルサインオン | × | × | ○ | ○ |
セッション | × | × | ○ | × |
Basic 認証とは
Basic 認証の仕組み
Basic 認証は以下の流れで行います。
Basic 認証を nginx で設定
nginx で Basic 認証を行う Web ページを作成してみます。
New password: Re-type new password:
server { listen 80; listen [::]:80; server_name _; root /usr/share/nginx/html; auth_basic "closed site"; # BASIC 認証を有効化 auth_basic_user_file /etc/nginx/.htpasswd; # .htpasswdファイルのパス
curl で Basic 認証を検証
Basic 認証が必要なことを通知
HTTP/1.1 401 Unauthorized (中略) WWW-Authenticate: Basic realm="closed site"
レスポンスより、以下の2点が確認できます。
- HTTP レスポンスステータスコードにより、401 認証エラーが発生
- WWW-Authenticate レスポンスヘッダにより、Basic 認証が必要
なお、ブラウザでアクセスすると以下のようになります。
Basic 認証でリクエスト
HTTP/1.1 200 OK
正しいユーザー名とパスワードを入力すると、無事アクセスできます。
補足: Authorization リクエストヘッダの内容
(中略) > Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
"dXNlcm5hbWU6cGFzc3dvcmQ" がセットされています。
これは、"username:password" を Base64 でエンコードした値です。
dXNlcm5hbWU6cGFzc3dvcmQ=
Digest 認証とは
Basic 認証で問題となった「ユーザー名とパスワード」が平文で送信されるという問題を解決した認証方式です。
なお、現在は、ほぼ全ての Web サイトが HTTPS で暗号化されているので、Digest 認証はあまり使われません。
(中略) or more rarely Basic access authentication.
https://en.wikipedia.org/wiki/Digest_access_authentication
These weak cleartext protocols used together with HTTPS network encryption resolve many of the threats that digest access authentication is designed to prevent.
Digest 認証の仕組み
Basic 認証との主な違いは、③で送信する認証情報の一部にハッシュ関数を利用することです。
②のレスポンスに含まれる WWW-Authenticate レスポンスヘッダの値
WWW-Authenticate レスポンスヘッダ | 役割 |
---|---|
Digest | Digest 認証が必要なことを伝える |
algorithm | ハッシュ関数に利用するアルゴリズム |
realm | レルム名 (認証が適用される範囲) |
qop | 保護レベル |
nonce | サーバーがランダムに生成する文字列 |
③のリクエストに含む Authorization リクエストヘッダの値
Authorization リクエストヘッダ | 役割 |
---|---|
rspauth | ハッシュ値 (求め方は後述) |
qop | 保護レベル |
cnonce | クライアントがランダムに生成する文字列 選択平文攻撃を回避するため |
nc (nonce count) | クライアントが nonce 値を使って送信した回数 |
なお、rspauth は以下の方法でハッシュ値を求めます。
A1 = ユーザ名 ":" realm ":" パスワード
https://datatracker.ietf.org/doc/html/rfc7616#section-3.4.1
A2 = ":" リクエスト URI ※qop=auth の場合
rspauth = MD5( MD5(A1) ":" nonce ":" nc ":" cnonce ":" qop ":" MD5(A2) )
A1: https://datatracker.ietf.org/doc/html/rfc7616#section-3.4.2
A2 (rspauth): https://datatracker.ietf.org/doc/html/rfc7616#page-15
https://wiki.suikawiki.org/n/rspauth%3D%22%22
Digest 認証を nginx で設定
nginx で Digest 認証モジュールは、こちら手順で build する必要があります。
今回は build 省略のため、以下の docker image を利用させていただきます。
New password: Re-type new password:
ユーザーのパスワードを設定します。今回は "password" とします。
curl で Digest 認証を検証
** 図①に相当 > GET / HTTP/1.1 ** 図②に相当 < HTTP/1.1 401 Unauthorized < WWW-Authenticate: Digest algorithm="MD5", qop="auth", realm="secret_area", nonce="1234567890" ** 図③に相当 > GET / HTTP/1.1 > Authorization: Digest username="username", realm="secret_area", nonce="1234567890", uri="/", cnonce="0987654321", nc=00000001, qop=auth, response="ABCDE", algorithm=MD5 ** 図④に相当 < HTTP/1.1 200 OK < Authentication-Info: qop="auth", rspauth="12345ABCDE", cnonce="0987654321=", nc=00000001
フォーム認証 (Cookie でセッション ID を管理) とは
認証結果は、Cookie (クライアント[=ブラウザ]に情報を保存する仕組み) で管理します。
Cookie には有効期限を設定でき、ログアウトする際は有効期限切れの Cookie をセットします。
フォーム認証の仕組み
なお、サーバーは Cookie の値 (セッション ID) をデータベース (RDB, KVS) に保存します。
クライアントはログイン済みを示す Cookie をセットすることで、2回目以降のアクセスで再ログインせずに済みます。
フォーム認証を Flask で設定
ログイン処理を実装するために、nginx の代わりに Flask (アプリケーションサーバー) を利用します。
from flask import Flask, request, make_response, render_template, redirect app = Flask(__name__) #以下の値は本来データベースから取得します username = "name" password = "pass" valid_cookie="1" @app.route("/") #トップページ def root(): if (request.cookies.get("session") == valid_cookie): #ログイン済みならホーム画面へ return redirect("/home") else: #ログインしてないならログインフォーム画面へ return render_template("index.html") @app.route("/login", methods=["POST"]) #ログイン処理 def login(): if (request.form["username"] == username and request.form["password"] == password): res = make_response(redirect("/home")) res.set_cookie("session","1") return res else: #ユーザー名かパスワードが間違ってる場合 return "login fail", 401 @app.route("/home") #ホーム画面 def home(): if (request.cookies.get("session") == valid_cookie): #ログイン済みならホーム画面 return "login success" else: #ログインしてないならフォーム画面へ return redirect("/") if __name__ == "__main__": app.run()
パス | ページの内容 | 動作説明 |
---|---|---|
/ | ログインフォーム | ユーザー名とパスワードを "/login" へ POST 有効な Cookie があると "/home" へ |
/login | ログイン処理 | ログインに成功すると Cookie を Set して "/home" へ ログインに失敗すると、401 エラーを表示 |
/home | ホーム画面 | 有効な Cookie があると、ページを表示 有効な Cookie が無いと、"/" へ |
<form method="POST" action="/login" name="login_form"> <input type="text" placeholder="username" name="username"> <input type="password" placeholder="password" name="password"> <input type="submit" value="login"> </form>
curl でフォーム認証を検証
ログインフォームを表示
<form method="POST" action="/login" name="login_form"> <input type="text" placeholder="username" name="username"> <input type="password" placeholder="password" name="password"> <input type="submit" value="login"> </form>
ログインフォームが表示されます。(ブラウザで表示すると以下)
ログインしてログイン後の画面を表示
** 図③に相当 > POST /login HTTP/1.1 ** 図④に相当 < HTTP/1.1 302 FOUND < Location: /home < Set-Cookie: session=1; Path=/ ** 図⑤に相当 > GET /home HTTP/1.1 > Cookie: session=1 ** 図⑥に相当 < HTTP/1.1 200 OK login success
ユーザー名とパスワードが正しければ、サーバーから Cookie が貰え、ホーム画面 (/home) にリダイレクトされます。
補足:Cookie がある時、無い時
login success
> GET /home HTTP/1.1 * < HTTP/1.1 302 FOUND < Location: / * > GET / HTTP/1.1 * < HTTP/1.1 200 OK <form method="POST" action="/login" name="login_form"> <input type="text" placeholder="username" name="username"> <input type="password" placeholder="password" name="password"> <input type="submit" value="login"> </form>
Bearer 認証 (+OAuth2.0/OIDC)
認可(Authorization・AuthZ) | 認証(Authentication・AuthN) | |
---|---|---|
目的 | 権限を付与 | ユーザーを識別および検証 |
利用例 | アプリケーションの閲覧権限の付与に利用 | ログインに利用 |
具体例 | ||
プロトコル | ・OAuth 2.0 ・SAML | ・OpenID Connect (OIDC) ・OAuth 2.0 (セキュアな OIDC を推奨) ・SAML |
利用するトークン | アクセストークン (OAuth 2.0) アサーション (SMAL) | ID トークン (OIDC) アサーション (SAML) |
Bearer 認証の仕組み
API サーバーは認証機能を持ちません。認可されたアクセストークンを元にアクセス許可を判断します。
また、アクセストークンを取得するためには、主に OIDC (推奨) や OAuth 2.0 が利用されます。
Bearer 認証を Authlib で設定
Bearer 認証を Authlib で設定する方法は、以下の記事で紹介しているのでご覧ください。
curl で Bearer 認証を検証
curl で Bearer 認証を検証する方法は以下の記事で紹介してるのでご覧ください。
関連記事
■Web サーバー構築
- 【Web サーバー構築1】Web/リバースプロキシ/ロードバランサー (nignx)
- 【Web サーバー構築2】プロキシ/リバースプロキシサーバー (squid)
- 【Web サーバー構築3】アプリケーションサーバー (gunicorn + Flask)
- 【Web サーバー構築4】HTTPS に対応 (Let’s Encrypt)
- 【Web サーバー構築5】Basic/Digest/Form/Bearer 認証 ←イマココ
- 【Web サーバー構築6】REST API サーバー(Flask)