mattintosh note

どこかのエンジニアモドキの備忘録

サイト全体にBasic認証をかけて特定のURLだけ認証を無効にしたい

WordPressEC-CUBE とかそういうの使ってると .htaccess にこんな風に書いてある。

WordPress: .htaccess

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

EC-CUBE 3.0.15: .htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !^(.*)\.(gif|png|jpe?g|css|ico|js|svg)$ [NC]
RewriteRule ^(.*)$ index.php [QSA,L]

EC-CUBE 4.0.5: .htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !^(.*)\.(gif|png|jpe?g|css|ico|js|svg|map)$ [NC]
RewriteRule ^(.*)$ index.php [QSA,L]

どっちも似たようなことを書いているけど、ざっくり説明すると「ファイルが存在しなければ全部 index.php に投げる」みたいな設定。(EC-CUBE の方は後方参照しないのに ^(.*)\. とか ^(.*)$ してて無駄だなと思う)

WordPress なら /2021/03/diary みたいなパスや、EC-CUBE なら /products/list みたいなパスで、実際にファイルやディレクトリが存在していないにもかかわらずページが表示されるのは index.php にリダイレクトされて index.php が受け取った情報によってページの振り分けをしているから。

さて、「サイト全体にベーシック認証をかけて特定のページだけ認証を無効にしたい(除外したい)」という話だけど、どんなときにそういうことが必要になるかというと、例えば EC-CUBE を使っていて決済通知を受け取るような場合に何を使っているかにもよって変わるけど、「ベリトランス」だと /shopping/vt3g_shopping_recv、「クロネコwebコレクト」だと /shopping/yamato_payment_recv、「GMOペイメントゲートウェイ」だと /shopping/gmo_payment_recv に決済通知システムからのリクエストが来る。ここにベーシック認証がかかっていると決済通知は認証を通れない。本番環境ではあまり無いだろうけど検証環境とかだとこういう構成が必要になってくることがある。

また、AWS などで Application LoadBalancer(以下 ALB)のヘルスチェックのために認証を外す必要がある。

以下、Apache 2.4 での話。

今回のような場合は下記のように書いたりするかもしれない。

Apache 2.4: .htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !^(.*)\.(gif|png|jpe?g|css|ico|js|svg|map)$ [NC]
RewriteRule ^(.*)$ index.php [QSA,L]

### ベーシック認証設定
AuthType Basic
AuthUserFile /var/www/.htpasswd
AuthName .
Require valid-user

### リクエストが ``/path/to/noauth'' だったら環境変数 ``noauth'' を定義する
SetEnvIf Request_URI ^/path/to/noauth$ noauth

### ローカルからのアクセスは許可
## Apache 2.2
#Allow from localhost
## Apache 2.4
Require local

### 環境変数 ``noauth'' が定義されていたら許可
## Apache 2.2
#Allow from env=noauth
## Apache 2.4
Require env noauth

上記の設定だと

  • localhost からのアクセスは無制限
  • それ以外のパスには Basic 認証がかかっているが /path/to/noauth は無効

と思われるが、実際には /path/to/noauth にアクセスしても Basic 認証が発生する。順を追ってみると下記のようになってるはず。

  1. /path/to/noauth にアクセスする
  2. 環境変数 noauth が定義される
  3. 環境変数 noauth がチェックされて許可
  4. /path/to/noauth は存在しないため RewriteRule によって index.php にリダイレクトされる
  5. index.php にアクセスする
  6. index.php では環境変数 noauth は定義されない
  7. 環境変数 noauth がチェックされて拒否

環境変数 noauto が定義されるのは /path/to/noauth だけであって index.php では発生しない。

「そうか!ならこうすれば良いんだな!」ってことで下記のようにしたとする。

Apache 2.4: .htaccess

SetEnvIf Request_URI ^/index.php$      noauth
SetEnvIf Request_URI ^/path/to/noauth$ noauth

一見これでも目的が達成できているように見える。しかし(いまどきブラウザに打ち込む人は少ないと思うけど)/index.php に直接アクセスすると認証をすり抜けるという罠がある。

とりあえず index.phpnoauth を設定をするのは止めて ApacheLogLeveldebug にしてログを見てみよう。

Apache 2.4: error_log (一部の情報を省略しています)

authorization result of Require valid-user : denied (no authenticated user yet) authorization result of Require local : denied authorization result of Require env noauth : granted ❶noauth が定義されている authorization result of <RequireAny>: granted ❷許可 authorization result of Require valid-user : denied (no authenticated user yet) authorization result of Require local : denied authorization result of Require env noauth : denied ❸noauth が定義されていない authorization result of <RequireAny>: denied (no authenticated user yet) ❹拒否

認証が 2 回行われていることや、❷で一度は granted されているのに❹で denied されていることがわかる。(ちなみにここの Require の順番は .htaccess に書いた順番)

index.phpnoauth が引き継げればいいのに…」と思った人は index.php

index.php

<?php phpinfo();

にして「Apache Environment」のセクションを見てみよう。(Basic 認証はクリアしても無効にしても可)

環境変数の一覧に定義した覚えのない REDIRECT_noauth という環境変数が見つかるはず。(_noauth 部分は定義した環境変数によって変わる)

これはリダイレクトした際に Apache が新たに定義する環境変数で、プレフィックスとして REDIRECT_ が付与される。他には REDIRECT_URLREDIRECT_STATUS などがある。

つまり最初に定義した noauth 環境変数index.php にリダイレクトした際に消えたのではなく REDIRECT_noauth に置き換えられたということ。なのでこれを許可してやればいいことになる。

SetEnvIf Request_URI ^/path/to/noauth$ noauth

Require env noauth
Require env REDIRECT_noauth

なお、Require env の書式は Require env env-env [env-var] ... となっているので下記のようにまとめて書いても良い。動作はドキュメントに Access is allowed only if one of the given environment variables is set. とある通り、どれか 1 つの環境変数が含まれていれば granted となる。

Require env noauth REDIRECT_noauth

もう一度 /path/to/noauth にアクセスすれば 1 回目は Require env noauth: granted、2 回目は Require env REDIRECT_noauth: granted となっているはず。

Apache 2.4: error_log (一部の情報を省略しています)

# /path/to/noauth の認証 authorization result of Require valid-user : denied (no authenticated user yet) authorization result of Require local : denied authorization result of Require env noauth: granted authorization result of : granted # index.php の認証 authorization result of Require valid-user : denied (no authenticated user yet) authorization result of Require local : denied authorization result of Require env noauth: denied authorization result of Require env REDIRECT_noauth: granted authorization result of : granted

直接 index.php にアクセスした場合は REDIRECT_noauth が存在しないので Basic 認証が発生する。


一応ネット上の情報も調べてみたが下記のように REDIRECT_STATUS でやっている人もいた。これは index.php にリダイレクトされたときに自動的に定義される REDIRECT_STATUS をリダイレクト元にも設定してしまえ、というもの…だと思う。

Apache 2.4: .htaccess

SetEnvIf Request_URI ^/path/to/noauth$ REDIRECT_STATUS
Require env REDIRECT_STATUS

一見お手軽そうだが、リダイレクト元で定義された REDIRECT_STATUS(暗黙的に REDIRECT_STATUS=1) は index.php にリダイレクトされると REDIRECT_プレフィックスが付与されて REDIRECDT_REDIRECT_STATUS=1 となり、index.php では新たに REDIRECT_STATUS=200 が定義されるため少々微妙に思える。

/path/to/noauth index.php
REDIRECT_STATUS 1 200
REDIRECT_REDIRECT_STATUS 未定義 1


まとめとして EC-CUBE で ALB のヘルスチェックファイルや「クロネコwebコレクト」、「GMOペイメントゲートウェイ」、「Paidy」の決済通知先を Basic 認証の対象外にする例を下記に書いておく。(私はネットワークレベルで制限するから .htaccess で設定しないんだけどレンタルサーバなら有用かも…)

Apache 2.4: .htaccess

AuthType Basic
AuthName .
AuthUserFile /var/www/.htpasswd

# デフォルトでは RequireAny なので
# ローカルからのアクセスや valid-user、環境変数(noauth または REDIRECT_noauth)が定義されていれば許可
Require local
Require valid-user
Require env noauth REDIRECT_noauth

# ロードバランサー ヘルチェックファイル用
SetEnvIf Request_URI ^/healthcheck.php$ noauth

# ベリトランス 決済通知用
SetEnvIf Request_URI ^/shopping/vt3g_payment_recv$ noauth

# クロネコwebコレクト 決済通知用
SetEnvIf Request_URI ^/shopping/yamato_payment_recv$ noauth

# GMO-PG 決済通知用
SetEnvIf Request_URI ^/shopping/gmo_payment_recv$ noauth

# Paidy 決済通知用
SetEnvIf Request_URI ^/paidy_webhook$ noauth

複雑な例として Apache環境変数に任意の値を使ったお遊びを下記に書いておく。

Apache 2.4: .htaccess

# アクセスを許可する URL (noauth 環境変数にレベル付する)
SetEnvIf Request_URI ^/path/to/1$ noauth=1
SetEnvIf Request_URI ^/path/to/5$ noauth=5

### index.php 用
# REDIRECT_noauth が ``1'' 以上
Require expr %{env:REDIRECT_noauth} -ge 1

### /path/to/1 用
# noauth 環境変数が ``1'' かつプライベート IP からのアクセス
<RequireAll>
    Require expr %{env:noauth} -eq 1
    Require ip 10 172.16.0.0/12 192.168
</RequireAll>

### /path/to/5 用
# noauth 環境変数が ``5'' かつローカルからのアクセス
<RequireAll>
    Require expr %{env:noauth} -eq 5
    Require local
</RequireAll>

Require env は書式が Require env env-var [env-ver] ...環境変数が定義されているかどうかで値のチェックができないため Require env noauth=1 のように書きたい場合は Require expr を使わなければならない。

ALB 使っていて mod_remoteip とか mod_extract_forwarded の設定をしてない人は IP で制限しようとすると面倒になるので注意した方がいいかも。