projects/ — como os teus projetos correm ao lado da plataforma em Docker — um compose por projeto, arranque, rotas, contrato de env e armadilhas.
Cada projeto em projects/ (criado no Studio — ver Primeiro projeto) tem o seu próprio compose, igrp-compose-<projeto>.yaml, e liga-se à rede e à infraestrutura da stack principal. Os frontends autenticam no Access Management e os backends publicam nele os seus recursos. Não há token estático — todo o M2M é client_credentials.
| Tipo | Rota | Cliente OAuth |
|---|---|---|
| Spring Boot | /gateway-api/<projeto> | próprio |
| Next.js | /apps/<projeto> | login pelo seeded · M2M próprio |
Backends Spring Boot
O que muda na v2
| Área | v1 | v2 |
|---|---|---|
| Biblioteca auth | core-spring-boot beta-12 | 0.2.0-beta.8 |
| Spring Boot / Java | 3.5.10 / 21 | 3.5.16 / Java 26 |
| Beans do framework | nenhum registado | @Import explícito |
| M2M | IGRP_ACCESS_M2M_SYNC_TOKEN | IGRP_ACCESS_M2M_CLIENT_ID / _SECRET |
AUTH_JWT_ISSUER | /auth/realms/igrp | /gateway-api/<ws>-access-management |
SPRING_APPLICATION_NAME | nome livre | igual ao client ID |
| Converter JWT | próprio | IgrpJwtAuthenticationConverter da livraria |
| Adaptador | — | igrp.authorization.service.adapter=igrp |
Java 26 é obrigatório
A cv.igrp.framework.auth:core-spring-boot:0.2.0-beta.8 é publicada com bytecode Java 26 (class file 70).
O sintoma não aponta para a causa
Com release 21 o javac rejeita-a (bad class file ... has wrong version 70.0) e o erro aborta a ronda de annotation processing — o que se manifesta como centenas de cannot find symbol em getters do Lombok. Quem vir isso vai procurar o problema no Lombok, e não está lá.
Requisitos: Spring Boot ≥ 3.5.16, <java.version>26</java.version>, e imagens de build e runtime com JDK/JRE ≥ 26.
Beans que têm de ser importados
A auto-configuração da livraria só declara o bean apiClient. O resto vive em cv.igrp.framework.auth.core, fora do component scan, e sem @Import nada é criado:
@Import({
AuthorizationSyncRunner.class, // publica recursos/permissões no arranque
IgrpAuthorizationService.class, // resolve roles/permissões no AM
IgrpAuthorizationServiceAdapter.class, // @ConditionalOnProperty(...="igrp")
IgrpJwtAuthenticationConverter.class, // fornece o JwtAuthenticationConverter
MethodSecurityEnableConfig.class, // @PreAuthorize sobre permissões IGRP
})Sem AuthorizationSyncRunner o sync falha em silêncio
Não aparece nenhuma linha [Authorization Sync] nos logs, e nada acontece.
SecurityConfig
A v2 exige o converter da livraria, que resolve roles pelo IAuthorizationServiceAdapter. Um JwtAuthenticationConverter próprio só lê authorities do JWT e tem de ser removido. O da livraria é injectado com ObjectProvider, porque é condicional:
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
ObjectProvider<JwtAuthenticationConverter> jwtAuthenticationConverter) { ... }Um SecurityConfig desactivado autentica tudo com HTTP Basic
Se as anotações estiverem comentadas e a segurança excluída em spring.autoconfigure.exclude, sem uma SecurityFilterChain o Spring Boot cai no default. Reactiva-o.
Annotation processor
A cv.igrp.framework.auth:core gera o PermissionsRegistry. Tem de estar em annotationProcessorPaths depois do Lombok e com o Lombok excluído — dois Lombok no processor path quebram a geração de getters. O maven-compiler-plugin tem de ser ≥ 3.14.1: a 3.11.0 não resolve uma versão gerida num annotationProcessorPath.
Propriedades
spring.application.name=${SPRING_APPLICATION_NAME:<projeto>}
igrp.access.api.base-url=${IGRP_ACCESS_API_BASE_URL}
igrp.access.m2m.client-id=${IGRP_ACCESS_M2M_CLIENT_ID:}
igrp.access.m2m.client-secret=${IGRP_ACCESS_M2M_CLIENT_SECRET:}
igrp.access.m2m.scope=${IGRP_ACCESS_M2M_SCOPE:}
igrp.authorization.service.adapter=${IGRP_AUTHORIZATION_SERVICE_ADAPTER:igrp}O igrp.access.m2m.sync-token foi removido de propósito: sem fallback, uma credencial em falta falha de forma visível em vez de degradar para o modo depreciado.
Eureka: a chave do mapa não faz binding por env var
EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE não funciona
O eureka.client.service-url é um Map. A env var produz a chave defaultzone em minúsculas, e o Eureka lê defaultZone — ficando com o default http://localhost:8761/eureka/. O sintoma é Cannot execute request on any known server mesmo com o servidor acessível.
Declara a zona no application-development.properties:
eureka.client.service-url.defaultZone=${EUREKA_SERVICE_URL:http://<slug>-eureka:8761/eureka/}Frontends Next.js
O migrador oficial faz o grosso
Não é preciso escrever código de auth à mão: o @igrp/framework-next-auth já traz withIGRPAuth(), createAuthProviderFromEnv(), refreshOidcAccessToken() e revokeOidcSession().
cd projects/theque
pnpm dlx @igrp/template-migrator@latest status
pnpm dlx @igrp/template-migrator@latest plan
pnpm dlx @igrp/template-migrator@latest applyO design system exige peers exactos
Sem eles o npm install recusa-se a resolver. E os lockfiles têm de ser regerados de raiz — um lockfile antigo bloqueia a resolução com um ERESOLVE que aponta para a versão velha. Se o projeto tiver package-lock.json e yarn.lock, deixa só o que o Dockerfile usa.
Contrato de env
AUTH_PROVIDER=igrp-auth
IGRP_AUTH_CLIENT_ID / _SECRET / _ISSUER / _SCOPES
NEXTAUTH_URL (com basePath E /api/auth) / NEXTAUTH_URL_INTERNAL / NEXTAUTH_SECRET
IGRP_ACCESS_MANAGEMENT_API
IGRP_SERVICE_ID / IGRP_M2M_CLIENT_ID / IGRP_M2M_CLIENT_SECRET
IGRP_APP_CODE / NEXT_PUBLIC_IGRP_APP_CODE
IGRP_PREVIEW_MODE / IGRP_SYNC_ACCESS / IGRP_SYNC_ON_CODE_MENUS / ..._MENU_ROLES
NEXT_IGRP_APP_CENTER_URL / NEXT_PUBLIC_BASE_PATH / NEXT_PUBLIC_IGRP_APP_HOME_SLUG- Login pelo cliente seeded, M2M pelo cliente próprio. O OIDC usa
ACCESS_MANAGEMENT_OAUTH_CLIENT_ID/SECRET, com os callbacks registados automaticamente; o sync usa o cliente próprio, com Basic em${IGRP_ACCESS_MANAGEMENT_API}/oauth2/tokene oIGRP_SERVICE_IDno headerX-Machine-Service-ID. IGRP_AUTH_SCOPES=openid email. O discovery anunciascopes_supported: ["openid"]; pedirprofilenão é seguro sem o registar no Authorization Server.IGRP_SYNC_ON_CODE_MENUS=trueempurrasrc/temp/menuse sobrescreve os menus no Access Management. Passa afalsequando o AM for a fonte de verdade.- O código lê
NEXT_IGRP_APP_CENTER_URL, nãoIGRP_APP_CENTER_URL. - O
NEXTAUTH_SECRETtem de ser o mesmo em todos os frontends do host, senão trocar de app força novo login.
Correções de código que o upgrade exige
O migrador não toca no código próprio da app:
| O que mudou | Efeito |
|---|---|
IGRPUserDTO.id passou de number a string | erro de tipos |
IGRPCombobox e IGRPDatePicker perderam gridSize | só sobrevive em IGRPRadioGroup; o className adjacente já define o span |
IGRPDatePicker passou a união discriminada | exige mode; startDate/endDate deram lugar a disableBefore/disableAfter |
| Guardas de sessão antigas | a função é hoje do IGRPSessionWatcher |
Dynamic Code Evaluation not allowed in Edge Runtime
Sob webpack, o bundle Edge de middleware.ts → lib/auth.ts pode falhar com este erro. A correcção é passar o build a next build --turbopack.
Correções no Dockerfile
O standalone fica aninhado
Com output: "standalone" e o turbopack.root do template, o server.js sai em .next/standalone/app/server.js. Sem ajuste o contentor morre com Cannot find module '/app/server.js'. Os estáticos e o public vão para ./app/... e o arranque passa a node app/server.js — também no command de warmup do compose.
NEXTAUTH_SECRET é preciso no estágio de build
O framework pré-renderiza /_not-found, esse render lê a sessão e falha com NEXTAUTH_SECRET must be set in production. O valor existe só no builder, que é descartado.
Arrancar
Com a stack principal a correr e o registo dos clientes OAuth concluído — um projeto novo só recebe o seu cliente no próximo arranque da stack principal.
No cartão do projeto, Deploy Docker constrói a imagem e arranca o projeto; depois, Open in Browser. O projeto aparece no grupo Project de Serviços → Visão Geral.
Usa sempre --build nos projetos
Os composes de projeto usam build:, por isso têm imagem própria. Um docker build -t <nome> avulso produz uma tag que o compose nunca usa, e o contentor continua a correr o código antigo. Passa --build na primeira vez e depois de mexer no código ou no Dockerfile.
Verificação
Backends:
[Authorization Sync] Authenticating to Access Management via client_credentials grant (clientId=...)
[Authorization Sync] Resource synchronization completed.
[Permission Sync] Successfully synchronized N permissions.N=0 é esperado enquanto as apps não tiverem anotações de permissão. A linha No client_credentials configured ... deprecated static-token mode significa que os segredos M2M não chegaram ao contentor.
Frontends: ✓ Ready + Application <APP_CODE> metadata synced successfully. + Access Management sync completed.
Rotas — sempre por ${DOCKER_IP}:${NGINX_HTTP_PORT}:
curl http://<slug>:<porta>/apps/<frontend>
curl http://<slug>:<porta>/gateway-api/<backend>/actuator/health
curl -o /dev/null -w "%{http_code}" http://<slug>:<porta>/apps/<frontend>/igrp/logo.pngUm CSS ou imagem a 404 significa imagem antiga — falta o --build.
Armadilhas da plataforma descobertas ao arrancar
Bugs pré-existentes que impediam a stack de subir, e que vale a pena reconhecer:
| Problema | Causa e correcção |
|---|---|
set: illegal option - numa tarefa de init | CRLF nos scripts montados — o shebang fica #!/bin/sh\r. Normalizar para LF e fixar por .gitattributes |
Healthcheck do Access Management sempre unhealthy | A imagem é distroless: sem shell nem curl. O command: era entregue à JVM como argumentos e nunca corria. Substituído pela tarefa -ready |
granted=0 e Permission 'igrp.client.list' not found | Defeito do primeiro arranque — um único restart resolve |
Nginx sempre unhealthy | Healthcheck na porta errada (:80 em vez de ${NGINX_HTTP_PORT}) |
| Redirect de login para um endereço interno | No nginx, um proxy_set_header dentro de um location descarta todos os headers do nível server. O bloco do Access Management só declarava X-Forwarded-Prefix; sem Host/X-Forwarded-Host o Host passava a $proxy_host, e como o AM se regista no Eureka com prefer-ip-address, os redirects absolutos saíam pelo IP do contentor. Os seis headers têm de ser repetidos no bloco |
authorization_request_not_found no callback | Cookie de sessão Secure sobre HTTP — a imagem traz server.servlet.session.cookie.secure=true e o browser descarta-o, criando sessão nova a cada pedido |
| Redirects pós-login do App Center para o contentor | NEXTAUTH_URL_INTERNAL apontava para o contentor. Tem de ser http://${DOCKER_IP}:${NGINX_HTTP_PORT} — confirmável em GET /api/auth/providers |
E no lado dos projetos
| Problema | Correcção |
|---|---|
| Perfil dev pede uma variável que o compose não dá | ${SERVER_PORT:${SERVICE_PORT:8080}} em vez de assumir uma delas |
| H2 fixo e exclusão da segurança em todos os perfis | mover para um perfil local explícito |
data.sql com INSERTs sem guarda e mode=always | duplica dados a cada reinício. O default passa a never |
| Eureka apontado ao host errado | usar o nome do serviço do workspace |
| Bucket MinIO que o script de init nunca cria | derivar a configuração de IGRP_OBJECT_STORAGE_* no compose |
IGRP_PREVIEW_MODE=true num frontend | desliga a autenticação. Nunca em nada que não seja uma demo |
Última atualização:
