私たちのウェブサイトのログインを処理する上流サーバーがあります。ログインに成功したら、ユーザーをサイトの安全な部分にリダイレクトします。ログインに失敗した場合、ユーザーをログインフォームにリダイレクトします。
アップストリームサーバーは、ログインに成功すると200 OK
を返し、ログインに失敗すると401 Unauthorized
を返します。
これは私の設定の関連部分です:
{
error_page 401 = @error401
location @error401 {
return 302 /login.html # this page holds the login form
}
location = /login { # this is the POST target of the login form
proxy_pass http://localhost:8080;
proxy_intercept_errors on;
return 302 /secure/; # without this line, failures work. With it failed logins (401 upstream response) still get 302 redirected
}
}
この設定は、ログインに成功したときに機能します。クライアントは302でリダイレクトされます。ログインに失敗すると、このしないが機能します。アップストリームサーバーが401を返し、error_page
が起動することを期待していました。ただし、302は引き続き取得されます。return 302 /secure/
行を削除すると、ログインページへのリダイレクトが機能します。したがって、どちらか一方しか持てないようですが、両方は持てません。
ボーナス質問;その名前の場所でerror_page
を処理する方法がThe Wayであるかどうか疑問です。私はこのようにそれを行うことで正しいですか?
編集:return
ブロックにlocation
があると、Nginxはproxy_pass
をまったく使用しなくなります。したがって、エラーページがヒットしないことは理にかなっています。ただし、これを行う方法に関する問題は残っています。
exact解決策は、NginxのLua機能を使用することです。
Ubuntu 16.04では、LuaをサポートするNginxのバージョンを次のようにインストールできます。
$ apt install nginx-extra
他のシステムでは異なる場合があります。 OpenRestyのインストールを選択することもできます。
Luaを使用すると、上流の応答に完全にアクセスできます。 $upstream_status
変数を介してアップストリームステータスにアクセスするにはappearを使用することに注意してください。そして、あなたがそうする方法ではあるが、 'if'ステートメントがNginxで評価される方法のため、 'if'ステートメントで条件付きで$upstream_status
を使用することはできません。
Luaを使用すると、構成は次のようになります。
location = /login { # the POST target of your login form
rewrite_by_lua_block {
ngx.req.read_body()
local res = ngx.location.capture("/login_proxy", {method = ngx.HTTP_POST})
if res.status == 200 then
ngx.header.Set_Cookie = res.header["Set-Cookie"] # pass along the cookie set by the backend
return ngx.redirect("/shows/")
else
return ngx.redirect("/login.html")
end
}
}
location = /login_proxy {
internal;
proxy_pass http://localhost:8080/login;
}
かなり簡単です。唯一の2つの癖は、POSTパラメータとクライアントへの最終応答でのCookieの設定を渡すためのリクエスト本文の読み取りです。
私が実際にを行ったのは、コミュニティからの大量の要求の後に、クライアント側でアップストリームストリームの応答を処理したということです。これは上流サーバーを変更せず、私のNginx設定を単純にしました:
location = /login {
proxy_pass http://localhost:8080;
}
要求を初期化するクライアントは、上流の応答を処理します。
<body>
<form id='login-form' action="/login" method="post">
<input type="text" name="username">
<input type="text" name="password">
<input type="submit">
</form>
<script type='text/javascript'>
const form = document.getElementById('login-form');
form.addEventListener('submit', (event) => {
const data = new FormData(form);
const postRepresentation = new URLSearchParams(); // My upstream auth server can't handle the "multipart/form-data" FormData generates.
postRepresentation.set('username', data.get('username'));
postRepresentation.set('password', data.get('password'));
event.preventDefault();
fetch('/login', {
method: 'POST',
body: postRepresentation,
})
.then((response) => {
if (response.status === 200) {
console.log('200');
} else if (response.status === 401) {
console.log('401');
} else {
console.log('we got an unexpected return');
console.log(response);
}
});
});
</script>
</body>
上記の解決策は、懸念事項を明確に分離するという私の目標を達成します。認証サーバーは、呼び出し元がサポートしたいユースケースに気づいていません。
私は@ michael-hamptonに完全に同意しますが、つまり、この問題はnginxで処理する必要がありますnotですが、error_page
をロケーションブロックに挿入します。
{
location @error401 {
return 302 /login.html # this page holds the login form
}
location = /login { # this is the POST target of the login form
proxy_pass http://localhost:8080;
proxy_intercept_errors on;
error_page 401 = @error401;
return 302 /secure/; # without this line, failures work. With it failed logins (401 upstream response) still get 302 redirected
}
}
以下が機能するかどうかは完全にはわかりませんが、Michaelの見解を共有しますが、 HTTP auth request module を使用してみることもできます。
location /private/ {
auth_request /auth;
...
}
location = /auth {
proxy_pass ...
proxy_pass_request_body off;
proxy_set_header Content-Length "";
# This is only needed if your auth server uses this information,
# for example if you're hosting different content which is
# only accessible to specific groups, not all of them, so your
# auth server checks "who" and "what"
proxy_set_header X-Original-URI $request_uri;
error_page 401 = @error401;
}
location @error401 {
return 302 /login.html
}
実際の認証を行うサーバーではなく、保護されたコンテンツをホストするサーバーで、この構成スニペットを使用します。現在、これをテストすることはできませんでしたが、これはまだいくつかの助けになります。
あなたのボーナスの質問に関して:はい、私の知る限り、これはこれが処理されるべき方法です。
return
は、書き換えによって生成されたものはすべて置き換えられるため、表示される動作は予想どおりです。
私はマイケルハンプトンに完全に同意しますが、あなたが本当にを他のオプションから外している場合は、以下に沿って何かを試すことができます。これは汚いハックであることを覚えておいてください、あなたは本当に最初にあなたのアーキテクチャを考慮する必要があります:
upstream backend {
server http://localhost:8080;
}
server {
location / {
proxy_pass http://backend;
if ($upstream_status = 401) {
return 302 /login.html;
}
if ($upstream_status = 200) {
return 302 /secure/;
}
}
}