Como proteger endpoints com @PreAuthorize, checklist ponta-a-ponta e resolução de problemas comuns.
7. Como funciona — ciclo de vida do pedido
Por cada pedido HTTP autenticado, o Spring Security executa este pipeline antes de o controlador ser chamado:
8. Proteger endpoints
Há duas formas equivalentes de aplicar uma permissão a um endpoint. Escolhe uma por controlador e mantém-te nela — misturá-las no mesmo handler é receita para confusão.
8.1 Com @PreAuthorize em controladores escritos à mão
O IgrpAuthorizationService está registado como bean com o nome igrpAuthorization. O SpEL T(Permission) resolve porque o MethodSecurityConfig.IgrpTypeLocator pré-registra o package cv.igrp.framework.auth.generated.
import cv.igrp.framework.auth.generated.PermissionsRegistry.Permission;
import org.springframework.security.access.prepost.PreAuthorize;
@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {
// Permissão única
@PostMapping
@PreAuthorize("@igrpAuthorization.checkPermission(T(Permission).MY_INVOICE_CREATE)")
public ResponseEntity<InvoiceDTO> create(@RequestBody InvoiceDTO dto) { ... }
// Todas (o utilizador tem de ter AMBAS)
@PostMapping("/{id}/approve")
@PreAuthorize("@igrpAuthorization.checkAllPermissions("
+ "T(Permission).MY_INVOICE_VIEW, "
+ "T(Permission).MY_INVOICE_APPROVE)")
public ResponseEntity<Void> approve(@PathVariable UUID id) { ... }
// Qualquer (o utilizador tem de ter PELO MENOS UMA)
@GetMapping("/{id}")
@PreAuthorize("@igrpAuthorization.checkAnyPermission("
+ "T(Permission).MY_INVOICE_VIEW, "
+ "T(Permission).MY_INVOICE_APPROVE)")
public ResponseEntity<InvoiceDTO> get(@PathVariable UUID id) { ... }
}8.2 Através do separador Security do iGRP Studio
Para controladores gerados pelo iGRP Studio, não anotes o código-fonte — o Studio sobrepõe-no na próxima regeneração. Abre o endpoint no Studio → separador Security:

- Permissões — em Adicionar Permissão escreve o nome de cada permissão (ex.:
pedidos.criar) e Adicionar. Em Operador de Permissões,ANDexige todas as permissões listadas eORbasta uma qualquer. - Funções — em Adicionar Função escreve o código de cada perfil (ex.:
ADMIN,OPERADOR). O utilizador tem de ter pelo menos um dos perfis listados.
A geração de código do Studio emite então o @PreAuthorize("@igrpAuthorization.check…") equivalente para permissões, ou @PreAuthorize("hasRole('...')") / hasAnyRole(...) para perfis, no método do controlador gerado. O comportamento em runtime é idêntico à forma manual acima.
8.3 Bypass de super-admin
IgrpAuthorizationService.checkPermission, checkAllPermissions e checkAnyPermission fazem todos short-circuit para true quando o chamador atual tem a authority IGRP_SUPER_ADMIN (da claim is_super_admin=true do JWT).
Todo o `@PreAuthorize` com `@igrpAuthorization.check…` permite implicitamente super-admins
Se não quiseres esse comportamento num endpoint específico, protege-o explicitamente contra uma authority que o super-admin não transporte, ou verifica !@igrpAuthorization.isSuperAdmin() a par do teu predicado de negócio.
O serviço expõe também isSuperAdmin() como predicado SpEL de primeira classe, útil em composições OR:
@PreAuthorize("@igrpAuthorization.isSuperAdmin() "
+ "or @igrpAuthorization.checkPermission(T(Permission).MY_INVOICE_APPROVE)")A revogação é uma alteração de dados no Access Management (remover a flag/perfil de super-admin do utilizador). Só produz efeito no JWT seguinte.
8.4 Verificações por perfil (role)
Se quiseres proteger por perfil em vez de permissão, usa os helpers SpEL padrão do Spring — os perfis do JWT chegam como authorities ROLE_* através do IgrpJwtAuthenticationConverter:
@PreAuthorize("hasRole('INVOICE_APPROVER')")
@PreAuthorize("hasAnyRole('INVOICE_APPROVER','FINANCE_ADMIN')")…ou combina ambos numa única expressão:
@PreAuthorize("hasRole('FINANCE_ADMIN') "
+ "or @igrpAuthorization.checkPermission(T(Permission).MY_INVOICE_APPROVE)")Prefere verificações por permissão para regras de negócio do dia-a-dia — são mais finas e sobrevivem a renomeações de perfis e reorganizações. Reserva as verificações por perfil para portões grosseiros do tipo "esta pessoa está, em termos gerais, dentro da equipa X".
9. Checklist ponta a ponta
Quando ligas um projeto alvo pela primeira vez, percorre esta lista uma vez:
mvn clean compile— confirma quetarget/generated-sources/annotations/cv/igrp/framework/auth/generated/PermissionsRegistry.javaexiste. Se não, ver §2.4.- Arranca a aplicação. Nos logs deves ver as linhas exatas emitidas pelo
AuthorizationSyncRunner:[Authorization Sync] Starting authorization synchronization with Access Management API... [Authorization Sync] Authenticating to Access Management via client_credentials grant (clientId=...) [Authorization Sync] Synchronizing resource for application 'my-invoice-service' [Authorization Sync] Resource synchronization completed. [Authorization Sync] Synchronizing N permissions for application 'my-invoice-service' [Permission Sync] Successfully synchronized N permissions. - Abre a UI do Access Management → Settings → separador Gestão de Acessos → seleciona um departamento → Permissions → Gerir Recursos → confirma que o teu
${spring.application.name}está listado e ativado, e que todas as tuas permissões aparecem para atribuição. - Separador Permissions → confirma que cada
@IgrpPermissionque definiste aparece com a descrição esperada. - Atribui uma permissão a um perfil no Access Management. Faz com que um utilizador nesse perfil obtenha um JWT pelo fluxo normal de login. Descodifica o JWT — o array
permissionstem de incluir o teu código de permissão. - Chama um endpoint protegido com esse JWT. Espera 200 ou 403, conforme a permissão esteja ou não concedida.
- Remove a permissão do perfil no Access Management. Volta a obter o JWT (um token em cache antigo continua a passar até expirar). Espera 403 no mesmo endpoint.
10. Resolução de problemas
| Sintoma | Causa provável | Onde procurar |
|---|---|---|
PermissionsRegistry não gerado em target/ | Caminho do processador de anotações em falta | pom.xml → annotationProcessorPaths (§2.4) |
[Permission Sync] Failed to synchronize… a cada arranque | Credenciais M2M erradas ou em falta | application.properties (§5); confirma que o cliente M2M está registado no Access Management com grant_type=client_credentials |
401 em todos os pedidos à tua API | Falha de validação do JWT — issuer não coincide | spring.security.oauth2.resourceserver.jwt.issuer-uri tem de ser igual à claim iss do JWT, byte a byte |
403 num endpoint protegido apesar de o perfil ter a permissão | JWT desatualizado; JWT não apanhado pelo IgrpJwtAuthenticationConverter; ou o nome da claim não coincide | Descodifica o bearer → verifica o array permissions; confirma que o IgrpJwtAuthenticationConverter está no classpath (core-spring-boot) |
SpEL T(Permission) não resolve no @PreAuthorize | MethodSecurityConfig não foi apanhado | Confirma que core-spring-boot está no classpath e que @EnableMethodSecurity está na tua SecurityConfig |
Utilizador criado no modelo de domínio com NPE em sub | IAMUserProfileSyncFilter fora da chain | SecurityConfig → addFilterAfter(iamUserProfileSyncFilter, BearerTokenAuthenticationFilter.class) |
| Permissão renomeada localmente, código antigo continua no JWT | O Access Management ainda tem o name antigo e o perfil continua a referenciá-lo | A renomeação tem 3 passos: código → arrancar para registar a nova permissão → o administrador reatribui aos perfis → remover o código antigo no release seguinte |
A aplicação não arranca: NoSuchBeanDefinitionException: ApiClient / NPE dentro de AuthorizationSyncRunner | igrp.access.api.base-url em falta — não tem default | Define a propriedade (ou IGRP_ACCESS_API_BASE_URL) com o URL base do Access Management |
Todos os @PreAuthorize passam para um utilizador específico | Esse utilizador tem is_super_admin=true — checkPermission/… faz short-circuit | Revoga o super-admin no Access Management e reemite o JWT (§8.3) |
hasRole('X') nega apesar de o utilizador ter o perfil X atribuído | O conversor só expõe ROLE_<selectedRole> (o perfil ativo), não o conjunto completo | Faz com que o utilizador escolha o perfil X no ecrã de login, ou verifica permissões em vez de perfis |
| O mesmo JWT devolve authorities desatualizadas após alterações no Access Management | As caches do adaptador (igrp.auth.*Cache) têm chave na string do JWT — um JWT rotado invalida naturalmente, um inalterado não | Limpa as caches manualmente ou aguarda a rotação do token; as entradas são despejadas com o TTL do token |
UUID.fromString(sub) no IAMUserProfileSyncFilter lança exceção | sub é a string do id interno do utilizador (pode não ser um UUID em todos os deployments) | Envolve em try/catch e deriva um UUID determinístico, ou torna t_iam_user_profile.id um String |
Se nenhum dos sintomas encaixar, ativa temporariamente o logging TRACE do Spring Security:
logging.level.org.springframework.security=TRACE
logging.level.cv.igrp.framework.auth=DEBUG…e procura o filtro/provider exato que correu. O output TRACE nomeia cada filtro e a decisão que devolveu para o pedido.
A seguir
- Frontend — Next.js — como as mesmas permissões protegem páginas, componentes e server actions.
- Access Manager — como o Access Management gere recursos, perfis, departamentos e menus.
Última atualização:
