Назад | Перейти на главную страницу

Различные перенаправления 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/ 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/;
         }
     }
}