web-dev-qa-db-ja.com

アップストリームプロキシ応答に基づく異なるNginxリダイレクト

私たちのウェブサイトのログインを処理する上流サーバーがあります。ログインに成功したら、ユーザーをサイトの安全な部分にリダイレクトします。ログインに失敗した場合、ユーザーをログインフォームにリダイレクトします。

アップストリームサーバーは、ログインに成功すると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をまったく使用しなくなります。したがって、エラーページがヒットしないことは理にかなっています。ただし、これを行う方法に関する問題は残っています。

7
harm

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>

上記の解決策は、懸念事項を明確に分離するという私の目標を達成します。認証サーバーは、呼び出し元がサポートしたいユースケースに気づいていません。

4
harm

私は@ 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
    }
}
1
2ps

以下が機能するかどうかは完全にはわかりませんが、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
}

実際の認証を行うサーバーではなく、保護されたコンテンツをホストするサーバーで、この構成スニペットを使用します。現在、これをテストすることはできませんでしたが、これはまだいくつかの助けになります。

あなたのボーナスの質問に関して:はい、私の知る限り、これはこれが処理されるべき方法です。

0
gf_

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/;
         }
     }
}
0
Peter Zhabin