A solução exata para a questão é usar os recursos Lua do Nginx.
No Ubuntu 16.04 você pode instalar uma versão do Nginx que suporta Lua com:
$ apt install nginx-extra
Em outros sistemas, pode ser diferente. Você também pode optar por instalar o OpenResty.
Com Lua você tem acesso total à resposta do upstream. Note que você aparece para ter acesso ao status upstream através da variável $upstream_status
. E de uma forma que você faz, mas devido à maneira como as instruções 'if' são avaliadas no Nginx, você não pode usar $upstream_status
na instrução 'if' condicional.
Com Lua, sua configuração será semelhante a:
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;
}
Bastante direto. As únicas duas peculiaridades são a leitura do corpo da solicitação para transmitir os parâmetros do POST e a configuração do cookie na resposta final ao cliente.
O que eu realmente acabei fazendo, depois de muita insistência da comunidade, é que lidei com as respostas do fluxo upstream no lado do cliente. Isso deixou o servidor upstream inalterado e minha configuração Nginx simples:
location = /login {
proxy_pass http://localhost:8080;
}
O cliente que inicializa o pedido manipula a resposta do upstream:
<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>
A solução acima atinge meu objetivo de ter uma clara separação de interesses. O servidor de autenticação ignora os casos de uso que os chamadores desejam suportar.