iGRPIGRP 3.0Documentation

Endpoints e resolução de problemas

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:

Pedido HTTPAuthorization: Bearer <JWT>SecurityFilterChainvalida assinatura e expiraçãoIgrpJwtAuthenticationConverterclaim permissions → authoritiesIAMUserProfileSyncFilterespelha o perfil do utilizador@PreAuthorizeigrpAuthorization.checkPermission(…)Método do controladorexecuta e responde 200browser ou outro serviçoSpring Securityframework (auth)projeto gerado · opcionalo teu controladorválidoautorizado401 · token inválido403 · sem permissão
O JWT é validado e as permissões viram authorities antes de o controlador correr. Um token inválido sai com 401; uma permissão em falta, com 403.

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:

Separador Security da ação criarPedido, com a permissão pedidos.criar e o operador OR
  • Permissões — em Adicionar Permissão escreve o nome de cada permissão (ex.: pedidos.criar) e Adicionar. Em Operador de Permissões, AND exige todas as permissões listadas e OR basta 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:

  1. mvn clean compile — confirma que target/generated-sources/annotations/cv/igrp/framework/auth/generated/PermissionsRegistry.java existe. Se não, ver §2.4.
  2. 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.
  3. 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.
  4. Separador Permissions → confirma que cada @IgrpPermission que definiste aparece com a descrição esperada.
  5. 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 permissions tem de incluir o teu código de permissão.
  6. Chama um endpoint protegido com esse JWT. Espera 200 ou 403, conforme a permissão esteja ou não concedida.
  7. 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

SintomaCausa provávelOnde procurar
PermissionsRegistry não gerado em target/Caminho do processador de anotações em faltapom.xml → annotationProcessorPaths (§2.4)
[Permission Sync] Failed to synchronize… a cada arranqueCredenciais M2M erradas ou em faltaapplication.properties (§5); confirma que o cliente M2M está registado no Access Management com grant_type=client_credentials
401 em todos os pedidos à tua APIFalha de validação do JWT — issuer não coincidespring.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ãoJWT desatualizado; JWT não apanhado pelo IgrpJwtAuthenticationConverter; ou o nome da claim não coincideDescodifica o bearer → verifica o array permissions; confirma que o IgrpJwtAuthenticationConverter está no classpath (core-spring-boot)
SpEL T(Permission) não resolve no @PreAuthorizeMethodSecurityConfig não foi apanhadoConfirma que core-spring-boot está no classpath e que @EnableMethodSecurity está na tua SecurityConfig
Utilizador criado no modelo de domínio com NPE em subIAMUserProfileSyncFilter fora da chainSecurityConfig → addFilterAfter(iamUserProfileSyncFilter, BearerTokenAuthenticationFilter.class)
Permissão renomeada localmente, código antigo continua no JWTO Access Management ainda tem o name antigo e o perfil continua a referenciá-loA 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 AuthorizationSyncRunnerigrp.access.api.base-url em falta — não tem defaultDefine a propriedade (ou IGRP_ACCESS_API_BASE_URL) com o URL base do Access Management
Todos os @PreAuthorize passam para um utilizador específicoEsse utilizador tem is_super_admin=true — checkPermission/… faz short-circuitRevoga o super-admin no Access Management e reemite o JWT (§8.3)
hasRole('X') nega apesar de o utilizador ter o perfil X atribuídoO conversor só expõe ROLE_<selectedRole> (o perfil ativo), não o conjunto completoFaz 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 ManagementAs caches do adaptador (igrp.auth.*Cache) têm chave na string do JWT — um JWT rotado invalida naturalmente, um inalterado nãoLimpa 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çãosub é 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.
How is this guide?

Última atualização: