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 libxadm-seguranca— o bean local com@Replacesda 0018 não existe mais, o app adotou o da lib e passou a declararapp.api-token): Bearer que confere →AuthenticationsystemcomROLE_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úblicoGET /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 libxadm-seguranca): lê o cookie, valida viaSessionTokenService(lib) e devolveAuthenticationcomROLE_VIEW+ popula os atributos de request (auth.email/auth.name) que oGlobalViewModelconsome. Só existe quandoauth.session-secretestá configurado. Oauth.issuer(claimiss) é obrigatório não-vazio no boot (defaultintegrador).ViewSecurityRule(SecurityRule) — tri-estado replicando o antigoAuthFilter:- 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 paraAPI_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-endGoogleLoginFlowIT/ViewSessionAuthIntegrationTest(+ rendering emviews/{LoginPageRenderingIT,NavbarRenderingIT}).