Eu tenho tentado resolver um problema semelhante. Meus usuários precisam ser autenticados para cada solicitação que fazem. Eu tenho me concentrado em conseguir que os usuários sejam autenticados pelo menos uma vez pelo aplicativo de back-end (validação do token JWT), mas depois disso, decidi que não precisava mais do back-end.
Eu escolhi evitar exigir qualquer plug-in Nginx que não esteja incluído por padrão. Caso contrário, você pode verificar os scripts nginx-jwt ou Lua e essas provavelmente seriam ótimas soluções.
Endereçando autenticação
Até agora, eu fiz o seguinte:
Delegou a autenticação ao Nginx usando auth_request
. Isso chama um internal
local que transmite a solicitação ao meu terminal de validação de token de back-end. Isso por si só não aborda a questão de lidar com um alto número de validações.
O resultado da validação do token é armazenado em cache usando uma proxy_cache_key "$cookie_token";
diretiva. Após a validação bem-sucedida do token, o back-end adiciona uma Cache-Control
diretiva que informa ao Nginx que apenas armazene em cache o token por até 5 minutos. Nesse ponto, qualquer token de autenticação validado uma vez está no cache, solicitações subsequentes do mesmo usuário / token não tocam mais no back-end de autenticação!
Para proteger meu aplicativo de back-end contra possíveis inundações por tokens inválidos, eu também armazeno em cache as validações recusadas quando meu ponto de extremidade de back-end retorna 401. Esses são armazenados em cache apenas por um curto período para evitar o preenchimento potencial do cache do Nginx com essas solicitações.
Adicionei algumas melhorias adicionais, como um ponto de extremidade de logoff que invalida um token retornando 401 (que também é armazenado em cache pelo Nginx) para que, se o usuário clicar em logout, o token não possa mais ser usado, mesmo que não tenha expirado.
Além disso, meu cache Nginx contém para cada token, o usuário associado como um objeto JSON, o que me impede de buscá-lo no banco de dados se eu precisar dessas informações; e também me impede de descriptografar o token.
Sobre a vida útil do token e atualizar tokens
Após 5 minutos, o token expirará no cache, portanto, o back-end será consultado novamente. Isso é para garantir que você consiga invalidar um token, porque o usuário efetua logout, porque foi comprometido e assim por diante. Essa revalidação periódica, com a implementação adequada no back-end, evita que eu precise usar tokens de atualização.
Tradicionalmente, os tokens de atualização seriam usados para solicitar um novo token de acesso; eles seriam armazenados no seu back-end e você verificaria se uma solicitação de um token de acesso é feita com um token de atualização que corresponda ao que você possui no banco de dados para esse usuário específico. Se o usuário efetuar logout ou tokens forem comprometidos, você excluirá / invalidará o token de atualização no seu banco de dados para que a próxima solicitação de um novo token usando o token de atualização invalidado falhe.
Em resumo, os tokens de atualização geralmente têm uma validade longa e são sempre verificados no back-end. Eles são usados para gerar tokens de acesso com uma validade muito curta (alguns minutos). Esses tokens de acesso normalmente atingem seu back-end, mas você apenas verifica a assinatura e a data de validade.
Aqui na minha configuração, estamos usando tokens com uma validade mais longa (pode ser horas ou um dia), que têm a mesma função e recursos que um token de acesso e um token de atualização. Como temos sua validação e invalidação armazenadas em cache pelo Nginx, elas são verificadas totalmente pelo back-end apenas a cada 5 minutos. Portanto, mantemos o benefício de usar tokens de atualização (poder invalidar rapidamente um token) sem a complexidade adicional. E a validação simples nunca chega ao seu back-end que é pelo menos uma ordem de magnitude mais lenta que o cache do Nginx, mesmo se usado apenas para verificação de assinatura e data de validade.
Com essa configuração, eu pude desativar a autenticação no meu back-end, pois todas as solicitações recebidas atingem a auth_request
diretiva Nginx antes de tocá-la.
Isso não resolve completamente o problema se você precisar executar qualquer tipo de autorização por recurso, mas pelo menos você salvou a parte básica da autorização. E você pode até evitar descriptografar o token ou fazer uma pesquisa no banco de dados para acessar os dados do token, pois a resposta de autenticação em cache do Nginx pode conter dados e transmiti-los ao back-end.
Agora, minha maior preocupação é que eu possa estar quebrando algo óbvio relacionado à segurança sem perceber. Dito isto, qualquer token recebido ainda é validado pelo menos uma vez antes de ser armazenado em cache pelo Nginx. Qualquer token temperado seria diferente, portanto, não atingiria o cache, pois a chave do cache também seria diferente.
Além disso, talvez valha a pena mencionar que uma autenticação do mundo real lutaria contra o roubo de token gerando (e verificando) um Nonce adicional ou algo assim.
Aqui está um extrato simplificado da minha configuração do Nginx para o meu aplicativo:
# Cache for internal auth checks
proxy_cache_path /usr/local/var/nginx/cache/auth levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=10m use_temp_path=off;
# Cache for content
proxy_cache_path /usr/local/var/nginx/cache/resx levels=1:2 keys_zone=content_cache:16m max_size=128m inactive=5m use_temp_path=off;
server {
listen 443 ssl http2;
server_name ........;
include /usr/local/etc/nginx/include-auth-internal.conf;
location /api/v1 {
# Auth magic happens here
auth_request /auth;
auth_request_set $user $upstream_http_X_User_Id;
auth_request_set $customer $upstream_http_X_Customer_Id;
auth_request_set $permissions $upstream_http_X_Permissions;
# The backend app, once Nginx has performed internal auth.
proxy_pass http://127.0.0.1:5000;
proxy_set_header X-User-Id $user;
proxy_set_header X-Customer-Id $customer;
proxy_set_header X-Permissions $permissions;
# Cache content
proxy_cache content_cache;
proxy_cache_key "$request_method-$request_uri";
}
location /api/v1/Logout {
auth_request /auth/logout;
}
}
Agora, aqui está o extrato de configuração para o /auth
terminal interno , incluído acima como /usr/local/etc/nginx/include-auth-internal.conf
:
# Called before every request to backend
location = /auth {
internal;
proxy_cache auth_cache;
proxy_cache_methods GET HEAD POST;
proxy_cache_key "$cookie_token";
# Valid tokens cache duration is set by backend returning a properly set Cache-Control header
# Invalid tokens are shortly cached to protect backend but not flood Nginx cache
proxy_cache_valid 401 30s;
# Valid tokens are cached for 5 minutes so we can get the backend to re-validate them from time to time
proxy_cache_valid 200 5m;
proxy_pass http://127.0.0.1:1234/auth/_Internal;
proxy_set_header Host ........;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Accept application/json;
}
# To invalidate a not expired token, use a specific backend endpoint.
# Then we cache the token invalid/401 response itself.
location = /auth/logout {
internal;
proxy_cache auth_cache;
proxy_cache_key "$cookie_token";
# Proper caching duration (> token expire date) set by backend, which will override below default duration
proxy_cache_valid 401 30m;
# A Logout requests forces a cache refresh in order to store a 401 where there was previously a valid authorization
proxy_cache_bypass 1;
# This backend endpoint always returns 401, with a cache header set to the expire date of the token
proxy_pass http://127.0.0.1:1234/auth/_Internal/Logout;
proxy_set_header Host ........;
proxy_pass_request_body off;
}
.
Endereçando a veiculação de conteúdo
Agora a autenticação está separada dos dados. Como você disse que era idêntico para todos os usuários, o próprio conteúdo também pode ser armazenado em cache pelo Nginx (no meu exemplo, na content_cache
zona).
Escalabilidade
Esse cenário funciona muito bem desde que você tenha um servidor Nginx. Em um cenário do mundo real, você provavelmente tem alta disponibilidade, o que significa várias instâncias do Nginx, potencialmente também hospedando seu aplicativo de back-end (Laravel). Nesse caso, qualquer solicitação que seus usuários fizerem poderá ser enviada para qualquer um dos servidores Nginx e, até que todos tenham armazenado em cache localmente o token, eles continuarão alcançando seu back-end para verificá-lo. Para um pequeno número de servidores, o uso dessa solução ainda traria grandes benefícios.
No entanto, é importante observar que, com vários servidores Nginx (e, portanto, caches), você perde a capacidade de efetuar logoff no lado do servidor porque não pode limpar (forçando uma atualização) o cache de tokens em todos eles, como /auth/logout
faz no meu exemplo. Você só tem a duração do cache de token de 5mn que forçará seu back-end a ser consultado em breve e informará ao Nginx que a solicitação foi negada. Uma solução parcial é excluir o cabeçalho ou o cookie do token no cliente ao fazer logout.
Qualquer comentário seria muito bem-vindo e apreciado!