我有一个上游服务器处理我们的网站login。 在成功login时,我想将用户redirect到网站的安全部分。 在login失败时,我想将用户redirect到login表单。
上游服务器在成功login时返回200 OK ,在失败login时返回401 Unauthorized 。
这是我的configuration的相关部分:
{ 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 } }
这个设置在成功login时有效。 客户端redirect到302.这在login失败时不起作用 。 上游服务器返回401,我预计error_page然后会踢。但我仍然得到302.如果我删除return 302 /secure/线redirect到login页面的作品。 所以似乎我可以有一个,但不是两个。
红利问题; 我怀疑我处理error_page的方式,那个命名的位置是方式。 我是否正确地这样做?
编辑 :原来在location块return使Nginx根本不使用proxy_pass 。 所以这是有道理的错误页面没有命中。 但是,如何做到这一点的问题仍然存在。
问题的确切解决scheme是使用Nginx的Luafunction。
在Ubuntu 16.04上,你可以安装一个支持Lua的Nginx版本:
$ apt install nginx-extra
在其他系统上可能会有所不同。 你也可以select安装OpenResty。
通过Lua,您可以完全访问上游响应。 请注意,您似乎可以通过$upstream_statusvariables访问上游状态。 在某种程度上,但是由于'if'语句在Nginx中的评估方式,您不能在'if'语句条件中使用$upstream_status 。
使用Lua,您的configuration将如下所示:
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; }
非常直截了当。 唯一的两个怪癖是读取请求体,以便传递POST参数以及在对客户端的最终响应中设置cookie。
在社区的许多推动下,我最终做的是我在客户端处理了上游的回应。 这使上游服务器保持不变,我的Nginxconfiguration变得简单:
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>
上面的解决scheme实现了我明确区分问题的目标。 authentication服务器不知道呼叫者想要支持的用例。
虽然我完全同意@ 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 } }
我不完全确定下面的工作是否正常,我同意迈克尔的观点,但是你可以尝试使用HTTPauthentication请求模块 ,也许是这样的一些东西:
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 }
您可以使用此configuration代码片段, 而不是在执行实际身份validation的服务器上,而是在承载受保护内容的服务器上。 我现在无法testing这个,但也许这对你有一些帮助。
关于你的奖金问题:是的,AFAIK,这是应该处理的方式。
你所看到的行为是预料之中的,因为return会取代你重写的东西。
虽然我完全同意迈克尔汉普顿,如果你真的没有其他select,你可以尝试以下几点。 请记住,这是一个肮脏的黑客,你首先需要考虑你的架构:
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/; } } }