Pular para conteúdo

Segurança (auth) — micronaut-security

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-07

O integrador usa o micronaut-security nativo (não mais filtros @Filter custom). Duas credenciais coexistem, cada uma resolvida por um bean próprio. A infra transversal de auth (sessão JWT, validação Firebase, settings) vem da lib da casa xadm-seguranca (br.com.xadm.comum.seguranca, constituição, Engenharia — ver 0018); a policy (whitelist, tri-estado, Bearer estático via @Replaces) fica local em br.com.xadm.comum.

/api/** — Bearer estático (integração server-to-server)

O agente on-prem X-Adm e o app autenticam em /api/** com um token fixo (não é JWT) em Authorization: Bearer <token>, valor de app.api-token (env INTEGRADOR_API_TOKEN, nome por destino). Sem fallback para o antigo API_BEARER_TOKEN — o ${INTEGRADOR_API_TOKEN:} do application.yml não é aninhado, então setar só o nome velho deixa o token VAZIO.

Token vazio em produção = boot recusado. Declarar app.api-token é declarar a feature: desde a xadm-seguranca 0.7.2 o BearerTokenGuard (bean @Context, eager) recusa o boot quando a property está declarada e resolve vazio. Antes disso o app subia healthy e o /api/** respondia 401 a tudo (o validador é fail-closed), e o sintoma chegava horas depois como "a integração parou". Em dev/test, ou com micronaut.security.enabled=false, o vazio só loga WARN — o validador fica dormente e /api/** responde como se não houvesse Bearer. A guarda também recusa o boot com placeholder ${...} não resolvido (desde a 0.5.1).

  • StaticBearerTokenValidator (TokenValidator, da lib xadm-seguranca — o bean local com @Replaces da 0018 não existe mais, o app adotou o da lib e passou a declarar app.api-token): Bearer que confere → Authentication system com ROLE_API; senão, sem autenticação.
  • ApiBearerSecurityRule (SecurityRule): para /api/**, exige autenticação — anônimo → REJECTED (401). Exceções: api.bearer.enabled=false (bypass, usado em teste) e o path público GET /api/v1/powersync/lastchange (só timestamp agregado, sem PII).

Views (/, MVC server-render JTE) — sessão Firebase

As telas server-side exigem sessão de login Google/Firebase (cookie xadm_session, JWT HS256, 8h). Ver o fluxo em Login Google.

  • SessionAuthenticationFetcher (AuthenticationFetcher, da lib xadm-seguranca): lê o cookie, valida via SessionTokenService (lib) e devolve Authentication com ROLE_VIEW + popula os atributos de request (auth.email/auth.name) que o GlobalViewModel consome. Só existe quando auth.session-secret está configurado. O auth.issuer (claim iss) é obrigatório não-vazio no boot (default integrador).
  • ViewSecurityRule (SecurityRule) — tri-estado replicando o antigo AuthFilter:
    • enabled (envs de auth completas): view exige autenticação;
    • bypass (dev/test sem envs): view liberada;
    • fail-closed (prod sem envs): view rejeitada (503).

Rejeições — ViewRejectionHandler

@Replaces o DefaultAuthorizationExceptionHandler e traduz as rejeições preservando o comportamento antigo:

Situação Resposta
autenticado sem permissão 403 (problem+json)
view anônima, browser (Accept: text/html ou sem Accept), com login configurado 302 /login?from=…
view anônima sem login configurado (dev e test, sem auth.*) 401 (problem+json) — a lib não registra o /login, e o redirect daria 404
view//api anônimo, script (JSON) 401 (problem+json)
view em fail-closed (prod sem envs) 503 (problem+json)

O corpo JSON é RFC 7807. Efeito preservado: uma rejeição de /api/health (Bearer ausente/errado) é gravada no ApiHealthRequestRecorder (tabela request).

Paths públicos — intercept-url-map

Os paths verdadeiramente públicos (estáticos + /login) são isAnonymous() no micronaut.security.intercept-url-map do application.yml: /health, /favicon.ico, /swagger/**, /swagger-ui/**, /css/**, /images/**, /login, /login/**, /logout. A ViewSecurityRule devolve UNKNOWN para os whitelisted (AuthSupport.isWhitelisted), deixando o intercept-url-map/Bearer decidir.

Config

  • api.bearer.enabled / app.api-token — Bearer do /api/** (INTEGRADOR_API_TOKEN; sem fallback para API_BEARER_TOKEN; vazio fora de dev/test recusa o boot).
  • auth.firebase-* / auth.session-secret / auth.cookie-secure — sessão de views.
  • micronaut.security.enabled: true, authentication: bearer, intercept-url-map.

Código e testes

  • Beans locais (policy per-app), br.com.xadm.comum.{ApiBearerSecurityRule, ViewSecurityRule, ViewRejectionHandler, AuthSupport, GlobalViewModel, LoginController}.
  • Beans da lib xadm-seguranca (infra transversal), br.com.xadm.comum.seguranca.{StaticBearerTokenValidator, BearerTokenGuard, SessionAuthenticationFetcher, AuthSettings, SessionTokenService, FirebaseIdTokenValidator, MicronautProfiles, AuthException} — ver 0018.
  • Testes (pacote br.com.xadm.comum, package-by-feature): SecurityRulesTest (regras/validador), AuthSupportTest (whitelist), LoginControllerTest, ApiBearerSecurityIntegrationTest, LoginRateLimitFilterTest, e os IT de auth end-to-end GoogleLoginFlowIT / ViewSessionAuthIntegrationTest (+ rendering em views/{LoginPageRenderingIT,NavbarRenderingIT}).