У меня есть вышестоящий сервер, обрабатывающий вход на наш сайт. При успешном входе в систему я хочу перенаправить пользователя в безопасную часть сайта. При ошибке входа в систему я хочу перенаправить пользователя в форму входа.
Вышестоящий сервер возвращает 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/
line работает перенаправление на страницу входа. Так что, похоже, у меня может быть одно, но не оба.
Бонусный вопрос; Я сомневаюсь, как я справляюсь с error_page
с этим названным местом является Путь. Правильно ли я поступаю так?
редактировать: Оказывается, return
в location
блокирует, что Nginx не использует proxy_pass
вообще. Поэтому имеет смысл не попасть на страницу с ошибкой. Однако остается проблема, как это сделать.
В точный Решение вопроса - использовать Lua-возможности Nginx.
В Ubuntu 16.04 вы можете установить версию Nginx, поддерживающую Lua, с помощью:
$ apt install nginx-extra
В других системах может быть иначе. Вы также можете выбрать установку OpenResty.
С Lua у вас есть полный доступ к ответам восходящего потока. Обратите внимание, что вы появиться чтобы иметь доступ к статусу восходящего потока через $upstream_status
переменная. И в некотором роде вы это делаете, но из-за того, как операторы if оцениваются в Nginx, вы не можете использовать $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;
}
Довольно прямолинейно. Единственные две причуды - это чтение тела запроса для передачи параметров 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, вы пробовали переместить 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
}
}
Я не совсем уверен, работает ли следующее, и я разделяю точку зрения Майкла, но вы можете попробовать использовать Модуль HTTP-запроса аутентификации, может быть, что-то в этом роде:
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
}
Вы бы использовали этот фрагмент конфигурации не на сервере, выполняющем фактическую аутентификацию, но на сервере, на котором размещается защищенный контент. Я не мог проверить это прямо сейчас, но, возможно, это все еще вам поможет.
Что касается вашего вопроса о бонусе: Да, AFAIK, это то, как с этим следует обращаться.
Вы видите ожидаемое поведение, так как 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/;
}
}
}