LoginSignup
2
2

More than 5 years have passed since last update.

Digest認証の確認環境、サーバ側とiOSクライアント環境

Last updated at Posted at 2018-10-15

発端

teratailの「Alamofireでdigest認証について」という質問でした。今までhttps+Basic認証しかやったことがなく、Digest認証自体知りませんでした。また、開発効率を考えswiftでWebアクセスすると言えばAlamofireのみでしかやっていなかったので、もう少しコア技術についても知りたいという点もあり、実際に調査してみたのが発端で、その過程で確認環境などを構築したので、それを公開してしまおうという流れです。

一式

githubに以下の説明に利用したファイル一式をアップしています。説明で省いた詳細をご確認いただきたい方はご覧ください。

サーバ側

nginxはデフォルトではDigest認証に対応していない様で、手っ取り早くNode.jsで実装しようとしたけれど、Node.jsを普段使いしていないので、全く手っ取り早くできず断念。。最終的にApache2.4で構築することに。

細かな設定は、既に色々と情報があるのでそちらに任せますが(例えば)、ポイントだけ提示したいと思います。

httpd.conf

dockerイメージのhttpd:2.4を使いますが、httpd.confはそのイメージに含まれているものをコピーして必要な部分を追記修正しました。
conf/extraに追記分だけ入れたいところですが、デフォルトでIncludeされているのは、conf/extra/proxy-html.confだけだったので、影響範囲を最小限にするという意図で直接httpd.confを修正してしまっています。

httpd.conf
# digest認証用のモジュールをロード
LoadModule auth_digest_module modules/mod_auth_digest.so

# 認証をかけたい場所(例として/digestとしている)に以下を設定
# さらに例としてhttpd.confに記載しているが、もちろん.htaccessでも良い
<Location /digest>
    AuthType Digest
    AuthName "private Web"
    AuthUserFile "/usr/local/apache2/conf/.htdigest"
    Require valid-user
</Location>

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Authorization}i\"" combined

Logフォーマット

発端となったDigest認証時のalgorithmがどの様に設定されているのかを表示させるため、LogFormatでAuthorizationヘッダを表示する様にしています。

docker

基本は上記のhttpd.confを読み込ませる様にする、そして認証するためのディレクトリを公開します。
./auth -> /usr/local/apache2/conf
./webroot -> /usr/local/apache2/htdocs

.htdigest

サーバ側で認証する際のユーザとパスワードを管理するファイルになります、名前はなんでもよく、前述したconfのAuthUserFileで指定したものになります。
Basic認証ではhtpasswdを利用することと同様にDigest認証でもhtdigestコマンドがあり、それを利用すれば良いのだけれど、自動化する際にはhtpasswdより使い勝手が悪いです。

htdigest -c .htdigest [[AuthName]] USERNAME

パスワードを標準入力から設定する必要があることと、結果に不要なデータ(’ー’)が含まれていたりします。
また、今回の環境作成では可能な限りシンプルに構築したかったので、別の方法を採用しています。結局MD5でハッシュしたものとのことと、環境依存性も考慮し、docker内で行いました。

docker-compose

最終的に、上記の様な環境を構築するためにdocker-composeを利用しました。そのymlを以下に提示します。

docker-compose.yml
version: "2"
services:
  digest:
    image: "httpd:2.4"
    environment:
      - HTTPD_DIGEST_PASS=pass
      - HTTPD_DIGEST_USER=admin
      - HTTPD_DIGEST_NAME=private Web
    volumes:
      - conf:/usr/local/apache2/conf
      - web:/usr/local/apache2/htdocs
    command: >
      bash -c 'echo -n $$HTTPD_DIGEST_USER:$$HTTPD_DIGEST_NAME:>conf/.htdigest &&
               echo -n $$HTTPD_DIGEST_USER:$$HTTPD_DIGEST_NAME:$$HTTPD_DIGEST_PASS|md5sum -|cut -f 1 -d " ">>conf/.htdigest
              '
  apache:
    image: "httpd:2.4"
    hostname: mobilework.kddilabs.jp
    ports:
      - 8888:8888
    volumes:
      - conf:/usr/local/apache2/conf
      - web:/usr/local/apache2/htdocs
    depends_on:
      - digest

volumes:
  conf:
    driver_opts:
      type: none
      device: $PWD/auth
      o: bind
  web:
    driver_opts:
      type: none
      device: $PWD/webroot
      o: bind

最初のdigestで.htdigestを生成、apacheでhttpサーバ用インスタンスを起動します。

一応正常にサーバ側が構築できたのかを確認するためにcurlを利用します。

curl -v --digest -u admin:pass http://localhost:8888/digest/

上記のコマンドでwebroot/digest/index.htmlに置いたindex.htmlの内容が表示されたらOKとなります。うまく動作しない場合には、標準出力に表示されたログを参照するとか、webroot/error内容を確認すると良いでしょう。

クライアント側

swiftでの実装ですが、基本はAppleのマニュアルFetching Website Data into Memoryを参考にしています。
Completion HandlerとDelegateを利用する2種類の方法があるが、今回詳細な状況を把握するためにDelegateを利用しました。URLSessionDataDelegateURLSessionTaskDelegateの二つで良いようです。

今回のポイントとして、認証時に呼び出されるurlSession(_:task:didReceive:completionHandler:)が重要となります。

    func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        print(#file, #function, #line, separator:":")
        print("authentication method: ", challenge.protectionSpace.authenticationMethod)
        print("protection space , host: ", challenge.protectionSpace.host)
        let cnt = challenge.previousFailureCount
        print("previous failure count: ", cnt)
        guard cnt == 0 else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        let cred = URLCredential(user: "admin", password: "pass", persistence: .forSession)
        completionHandler(.useCredential, cred)
    }

URLCredentialのインスタンス生成時に引き渡すユーザ・パスワードを.htdigest作成時と一致させることになります。

クライアント側は短いですがこれだけ。ソースを見るのが一番ですね。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2