会話で「Basic認証のような手法ですね」という話が出てきたが、勉強不足でBasic認証とはユーザー名とパスワードをブラウザに入力するやつ、くらいの認識しか持っていなかったので、改めてBasic認証とはなにかを調べて書く。
Basic認証とはなにか
Mozilla様の記事が分かりやすかった。いつもありがとう。
https://developer.mozilla.org/ja/docs/Web/HTTP/Authentication
RFCも概要部分はいつもお世話になる。
https://datatracker.ietf.org/doc/html/rfc7617
概要部分からは、ユーザー名とパスワードをBase64エンコードして送ることが分かる。
This document defines the “Basic” Hypertext Transfer Protocol (HTTP) authentication scheme, which transmits credentials as user-id/password pairs, encoded using Base64.
もう少し下を読むと、認証はrealmと呼ばれる領域に対して掛けられることが分かる。これらをざっくり知った上で、もう少し以下の流れで記事を書いてみようと思う。
- HTTP認証の流れを再確認する
- Dockerを使って実際にBasic認証を試す
HTTP認証の流れを再確認する
先ほどのMozillaの記事を再掲。
https://developer.mozilla.org/ja/docs/Web/HTTP/Authentication
HTTPは以下のような認証フレームワークを持っている。
- クライアントが
GETリクエストを送る - サーバは
401を返す。その際にヘッダーWWW-Authenticateを付与し、認証方法を指定する - クライアントは認証方法に従って認証にチャレンジする。その際に
Authorizationヘッダーを使うのが一般的
OKなら200が返るというわけだ。
WWW-Authenticateヘッダーの存在は初めて知った。ここで認証方法が指定されており、例えばBasicやDigest、Bearerなどが指定される。形式としては以下のような形を取る。
WWW-Authenticate: <type> realm=<realm>
<type>は先ほど書いた例が入る。<realm>は保護領域を表す値が入っている。これになんの意味があるかと言うと、クライアント側はどこに対して認証しようとしているのかが明確になる点が嬉しい。サーバー側としては、realmをつかって保護する領域を分け、それぞれに対して認証を分けて管理することができる。
例えば、サーバー側のリソースのうち/hogeと/fugaを保護したい場合、前者をrealm=hogeでBasic認証を、後者をrealm=fugaにしてDigest認証をかけることができる。保護のレベルを分けたいときに有効そう(小並感)。
続いてAuthorizationヘッダーを以下の形式で用意してチャレンジする。
Authorization: <type> <credential>
個人的にはAuthenticationとAutorizationが混じっててややこしい。Basic認証の場合は、ユーザー名とパスワードを:でつないだ文字列をBase64エンコードした値が<credential>の部分に入る。Base64エンコードされているだけなので、HTTPS通信などの暗号化された通信で使わないとセキュリティ的に危ない。
※Base64とはなにか
Wikipediaが分かりやすかった。
https://ja.wikipedia.org/wiki/Base64
Base64はエンコード方式であり、アルファベット、数字、+,/の64種類の文字でデータを表す。変換時に4文字ずつ変換し、文字数が足りない時は=が使われるので実は65文字だったりする。
6bitずつ分けるのは、6bit = 2^6 = 64種類で64種類の文字のどれかに必ず変換できるから。
どんなデータも64種類のASCII文字に変換できるので、ASCIIしか扱えない場合に便利。例えばメールプロトコルはASCIIしか扱えないらしい。
Dockerを使って実際にBasic認証を試す
一応Dockerで設定してみる。UbuntuとApacheを使う。
まずDockerを起動する。
$ docker run -it ubuntu:latest
-itオプションはおまじないのように使いがちだけど、ここらで理解する。
-iオプションは interactive の略で、コンテナの標準入力(STDIN)をクライアントの標準入力にアタッチするオプション。なお、これがなくても標準出力(STDOUT)と標準エラー(STDERR)はクライアントの標準出力にアタッチされているので、コンテナの出力を見ることができる。
-tは擬似端末をコンテナに割り当てるオプション。これがないとコンテナは端末を持たない。つまり対話型インターフェースを持たなくなるということ。「疑似」なのはおそらく物理デバイス特別するため。我々は端末のことをいちいち擬似端末とは呼ばないけどね。
-iだけ指定すると入力を渡せるけど対話インターフェースである端末がない状態になる。-tだけしているすると端末が割り当てられるがこちらからのコマンドを送ることができない。
続いて、apache httpサーバーをインストールする。apache httpサーバーはインストールすると勝手に実行するらしいが念のため再起動する。
$ apt install -y apache2
$ service apache2 restart
続いてBasic認証が必要なページを作っていく。apache httpサーバーにおいてページの管理は/etc/apache2/sites-availableというディレクトリで行われている。すでに000-default.confがいてデフォルトのルートディレクトリなどを設定している。今回は新たにページを追加する。
$ vim /etc/apache2/sites-available/basic-auth-test.conf
<Directory /var/www/html/basic-auth>
AuthType Basic
AuthName "Basic Authentication desuyo!!"
AuthUserFile /etc/apache2/myhtpasswd
require valid-user
</Directory>
続いて先ほど指定したAuthUserFileに実際にBasic認証用のパスワードファイルを作成する。htpasswdコマンドはBasic認証のファイルを管理するコマンド。
$ htpasswd -c /etc/apache2/myhtpasswd test
その後パスワードを入力
続いて先ほど指定したDirectoryにページコンテンツを適当に置き、sites-enabledからsites-availableにシンボリックリンクを張る。そうすることで正式にサーバーとしてコンテンツを返せるようになる。
$ vim /var/www/html/basic-auth/index.html
$ a2ensite basic-auth-test
$ service apache2 reload
curlでBasic認証の動きを確かめる
先ほど設定したディレクトリに対してcurlリクエストを投げると、401 Unauthorizedが返ってくる。
$ curl -v localhost/basic-auth
* Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /basic-auth HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Date: Sat, 13 Jan 2024 06:45:53 GMT
< Server: Apache/2.4.41 (Ubuntu)
< WWW-Authenticate: Basic realm="Basic Authentication desuyo!!"
< Content-Length: 456
< Content-Type: text/html; charset=iso-8859-1
<
以下省略
ちゃんとWWW-Authenticate: Basic realm="Basic Authentication desuyo!!"が返ってきている。curlは-uでユーザー名とパスワードを送るので送ってみると、200 okが返る。
$ curl -u test:<設定したパスワード> localhost/basic-auth/
コンテンツが表示される
終わりに
Basic認証を学ぶことで、HTTP認証の流れを知ることができた。