Claim
Quando usar: sempre que o handler precisa saber quem está chamando. O gateway verifica o
token do usuário e cunha o contexto; o serviço confere de novo e entrega o resultado no
ServiceContext. Nada disso vem do corpo, da query ou de um cabeçalho solto.
O que chega no handler
ServiceContext é um record com os claims já interpretados:
| Campo | O que é |
|---|---|
principal | Quem age: id (user:<realm>:<sub>, svcacct:<realm>:<client_id>, service:<iss>), type, realm, subject, name. |
tenant | O tenant efetivo (id, uuid, slug) — o de origem, ou o atuante em admin_context. |
admin | Staff da operação atuando num tenant de cliente: acting e o tenant de origem. |
platform | O principal é de plataforma (issuer do realm de plataforma). |
roles | Os papéis do produto, já expandidos. Informativo. |
permissions | As permissões qualificadas do produto, já expandidas. É o que autoriza. |
route | A rota que o gateway casou (o serviço confere contra o próprio roteador). |
authMethod | console_session, bearer… |
expires, correlationId, requestId, actionId | Prazo e os ids de correlação. |
O tenant vem daqui — sc.requireTenantId() — e é ele que escopa toda consulta:
@NnsRoute("items.get")
Views.ItemView get(Ctx ctx, ServiceContext sc, Match m) {
String tenant = sc.requireTenantId(); // sem tenant no contexto: 403, não uma consulta ampla
return Views.ItemView.of(ItemStore.get(ctx, service.db, tenant, m.params().get("item_id"), false));
}
Expondo o contexto (útil para depurar e para o cliente saber o que pode)
nnp/api.yaml:
routes:
- {id: whoami, method: GET, path: /whoami, permission: sample.context.read}
java/src/main/java/.../app/ItemsController.java:
@NnsRoute("whoami")
Views.WhoamiView whoami(ServiceContext sc, Introspection who) {
return Views.WhoamiView.of(who, sc, service.module.iam(), service.roleText(), SampleModule.MARKER, ScanEngine.ENGINE);
}
java/src/main/java/.../app/Views.java:
static WhoamiView of(
Introspection who, ServiceContext sc, Iam iam, String role, String marker, String engine) {
Map<String, Boolean> verified = new TreeMap<>();
for (Permission p : iam.permissions()) {
String q = p.id();
if (!Descriptor.productOf(q).equals(iam.product())) {
q = iam.product() + "." + q;
}
verified.put(q, sc.can(q));
}
return new WhoamiView(
who.serviceId(),
who.releaseId(),
who.version(),
who.routeId(),
sc.tenantId(),
sc.principal().id(),
sc.principal().type(),
sc.platform(),
sc.admin() != null && sc.admin().acting(),
sc.authMethod(),
sc.requestId(),
sc.correlationId(),
role,
marker,
engine,
sc.roles(),
verified);
}
Três decisões que valem a pena copiar:
- A vista é explícita. Só os campos que o contrato publica saem na resposta; o resto do contexto fica no serviço.
verifiedé o que a chamada TEM, permissão por permissão — é o que permite ao front esconder o que não pode ser feito (e é só isso: esconder).- O
principal.idpode ser longo e opaco (user:<realm>:<uuid>). Use-o como identidade, não como nome de exibição.
Como testar
O teste do contexto é o teste HTTP com token de verdade:
@Test
void whoamiShowsTheVerifiedContextOfTheToken() {
String token = ctxToken(ctx("whoami", "sample.context.read", "sample.item.read"), "GET", "/v1/sample/whoami", null);
HttpResponse<String> r = call("GET", "/v1/sample/whoami", token, null);
assertThat(r.statusCode()).isEqualTo(200);
JsonNode b = body(r);
assertThat(b.get("tenant_id").asText()).isEqualTo("4");
assertThat(b.get("principal_id").asText()).startsWith("user:nnumbers:");
assertThat(b.get("auth_method").asText()).isEqualTo("console_session");
assertThat(b.get("verified").get("sample.context.read").asBoolean()).isTrue();
assertThat(b.get("verified").get("sample.item.scan").asBoolean()).isFalse();
}
O tenant do teste é o do token: ctx(...) monta o contexto com new ServiceContext.Tenant("4", "", "cliente")
e o token é assinado com a chave do gateway. Trocar o tenant no corpo da requisição não muda nada —
e é exatamente isso que o teste prova.
Erros comuns
- Ler
tenant_iddo corpo, da query ou de um cabeçalho (X-Tenant-Id,X-User) — o gateway não repassa cabeçalho de identidade, e o serviço não deve acreditar em nenhum. O tenant é sempresc.requireTenantId(). - Trocar
requireTenantId()porsc.tenant().id()— a segunda forma devolvenullquando não há tenant e o erro aparece longe, comoNullPointerExceptionem vez de 403. - Confiar em
sc.roles()— papel é agrupamento; quem autoriza é a permissão (veja Permissão). - Achar que
platformé "admin" — é o issuer do realm de plataforma. Um principal de plataforma continua precisando da permissão da rota. - Ignorar
admin_context— quando o staff da operação atua num tenant de cliente, o efetivo é o tenant do cliente eadmin.acting()étrue: audite os dois (o evento de audit já levaadmin_context). - Montar o contexto à mão no handler — quem monta é o runtime, a partir do token verificado.
Esta página ajudou?
Reportar um problema nesta páginaNão envie senhas, chaves, tokens ou dados de clientes.