基于上游代理响应的不同 Nginx 重定向

har*_*arm 7 nginx proxy redirect

我有一个上游服务器处理我们网站的登录。成功登录后,我想将用户重定向到站点的安全部分。在登录失败时,我想将用户重定向到登录表单。

上游服务器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
    }
}
Run Code Online (Sandbox Code Playgroud)

此设置在成功登录时有效。客户端使用 302 重定向。这在登录失败时不起作用。上游服务器返回 401,我预计它error_page会启动。但我仍然得到 302。如果我删除该return 302 /secure/行,则重定向到登录页面会起作用。所以似乎我可以拥有其中一个,但不能同时拥有。

奖金问题;我怀疑我处理该error_page命名位置的方式是 The Way。我这样做是否正确?

编辑:原来returnlocation块中有 a使得 Nginx 根本不使用proxy_pass。所以没有命中错误页面是有道理的。然而,如何做到这一点的问题仍然存在。

har*_*arm 5

该问题的确切解决方案是使用 Nginx 的 Lua 功能。

在 Ubuntu 16.04 上,您可以安装支持 Lua 的 Nginx 版本:

$ apt install nginx-extra
Run Code Online (Sandbox Code Playgroud)

在其他系统上可能会有所不同。您也可以选择安装 OpenResty。

使用 Lua,您可以完全访问上游响应。请注意,您似乎可以通过$upstream_status变量访问上游状态。并且在某种程度上您可以这样做,但是由于在 Nginx 中评估“if”语句的方式,您不能$upstream_status在“if”语句条件中使用。

使用 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;
    }
Run Code Online (Sandbox Code Playgroud)

很直接。唯一的两个怪癖是读取请求正文以传递 POST 参数以及在对客户端的最终响应中设置 cookie。


经过社区的大力推动,我实际上最终做的是在客户端处理上游流响应。这让上游服务器保持不变,我的 Nginx 配置很简单:

location = /login {
       proxy_pass http://localhost:8080;
}
Run Code Online (Sandbox Code Playgroud)

客户端初始化请求处理上游响应:

  <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>
Run Code Online (Sandbox Code Playgroud)

上面的解决方案实现了我的目标,即明确分离关注点。身份验证服务器不知道调用者想要支持的用例。


2ps*_*2ps 1

虽然我完全同意@michael-hampton,即这个问题不应该nginx 处理,但您是否尝试过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
    }
}
Run Code Online (Sandbox Code Playgroud)