0015 — Filtros admin com @ServerFilter (honra @Order)¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-04 · Decidido em: 2026-07-04
Contexto¶
O browser da Central (central.xadm.biz) passou a receber "No 'Access-Control-Allow-Origin'
header is present" em toda chamada a /api/admin/**. O preflight OPTIONS respondia 401
em produção, apesar de o código estar correto e o teste
AdminControllerTest.cors_options_preflight_returns200WithHeaders passar verde.
A causa foi confirmada empiricamente rodando o fat jar (o mesmo artefato que o Coolify
builda) localmente: o OPTIONS caía no AdminAuthFilter (401) antes
do AdminApiCorsFilter, que deveria curto-circuitar o preflight.
Os dois filtros usavam a API legada @Filter + implements HttpServerFilter, ordenados só por
@Order. O HttpServerFilter legado ordena pelo método getOrder() e ignora a anotação
@Order. Com ambos no default, a ordem ficava indefinida — e divergia por ambiente:
- Classpath (teste
@MicronautTest): a varredura punha o CORS antes → preflight tratado → verde. - Fat jar (produção): a ordem das entradas do zip invertia → Auth primeiro → 401.
Por isso o gate nunca pegou: teste e produção exercitam ordens de filtro diferentes.
Decisão¶
Migrar os dois filtros admin para a API @ServerFilter do Micronaut 5
(@RequestFilter / @ResponseFilter), que honra @Order de forma determinística tanto no
classpath quanto no fat jar. Ordem: AdminApiCorsFilter = HIGHEST_PRECEDENCE, AdminAuthFilter
= HIGHEST_PRECEDENCE + 100.
Como defesa-em-profundidade, o AdminAuthFilter exime OPTIONS (passa adiante): um
preflight nunca leva Authorization, então negá-lo com 401 é sempre errado — e assim, mesmo que
a ordem se inverta de novo, o preflight não quebra.
A decisão 0007 (admin protegido por filtro JWT) continua de pé; muda só a API de filtro e a garantia de ordem.
Consequências¶
- Regressão travada por teste que reflete produção:
AdminFilterOrderingTestverifica o invariante de ordem (uso de@ServerFilter+ CORS antes do Auth) e a eximição deOPTIONS— guardas que o teste de integração antigo, dependente da sorte do classpath, não dava. - Respostas de erro (ex.: 401) em
/api/admin/**agora também carregamAccess-Control-Allow-Originquando a origem é permitida — o browser consegue ler o erro em vez de mascará-lo como falha de CORS. - Os demais filtros legados (
AppUserAuthFilter,SetupAuthFilteremcentralapps) têm o mesmo risco latente de ordem indefinida se algum dia dividirem path com outro filtro. Não foram migrados aqui (sozinhos no path, sem sintoma) — migrar é trabalho futuro.
Alternativas consideradas¶
- Sobrescrever
getOrder()mantendoHttpServerFilter: corrige a ordem (verificado no fat jar), mas mantém a armadilha — o próximo filtro legado com só@Ordervolta a falhar em silêncio. A migração para@ServerFilterremove a classe do bug. - Só eximir
OPTIONSno Auth, sem mexer na ordem: resolveria o preflight, mas deixaria a ordem dos dois filtros indefinida para o resto (ex.: decoração de resposta). Insuficiente sozinha.