↑ ↓ para mover · Enter para abrir · Esc para fecharToque em um resultado

Arquitetura
skill · classe design · opus

security-architect

Projeta quem pode chamar o quê: o mecanismo, as authorities, a filter chain e as respostas de falha.

skill · designopusescreve 32-seguranca.mdsó dentro de docs/use-cases/UC-*/**
§ o arquivo que ela escreve

32-seguranca.md, bloco a bloco

Um parcial real: UC-006-anonymize-customer, no bank-app, em trechos. Passe o mouse ou clique num bloco para ver o que a skill fixa ali. As seções do arquivo também são chips: clique em um para ir até ela.

bank-app/docs/use-cases/UC-006-anonymize-customer/32-seguranca.md

UC-006 · Security

Partial of docs/use-cases/UC-006-anonymize-customer/. Owner: security-architect. Inherits the endpoints from 30-rest.md and the domain from 10-dominio.md — doesn't redecide them. Contains no code. Form references in .claude/skills/security-architect/templates/. Rules applied, never reproduced: @.claude/rules/authorization.md, @.claude/rules/api-rest.md § Errors, @.claude/rules/transport-security.md § HSTS.
FieldValue
Mother spec00-caso-de-uso.md — Access: only callers with the DATA_PROTECTION_OFFICER role; JWT issued by the bank's identity provider
Why this partial existsAccess names a role, and the project has no SecurityFilterChain yet — first secured case, so this partial also owns the project-wide setup and every endpoint already on disk
Stackservlet (spring-boot-starter-webmvc in pom.xml, Boot 4.1.1)
Wiring packageinfrastructure.config.security (blueprint packages.map, entry infrastructure.config)
Divergences from other partialsnone. 00-caso-de-uso.md listed "caller not identified, or without the role" as one situation; here it is two statuses, 401 and 403 (§ 6)

## 1 · Access per endpoint

MethodPathoperationIdRuleEnforced inMeta-annotationNo credentialWithout authorityWith authority
DELETE/api/v1/customers/{customerId}anonymizeCustomerrole DATA_PROTECTION_OFFICERchain: request rule DELETE /api/v1/customers/* → hasRole(Roles.DATA_PROTECTION_OFFICER)—401403204
POST/api/v1/customerscreateCustomer (UC-001)publicchain: permitAll—201—201
GET/api/v1/customers/{customerId}getCustomer (UC-003)authenticated (changed, user's decision)chain: deny by default (anyRequest().authenticated())—401— (no role)200

The role check is a request rule in the chain, not method security: one endpoint, one role, matched by method and path. No @PreAuthorize in the project yet, so no MethodSecurityConfig and no method-security denial to translate — a later case that needs one adds them. A 403 is not replaced by 404: the resource's existence is not sensitive (the DELETE answers 204 for an unknown id anyway, 30-rest.md).

Public rows of the project after this case (each with its reason):

PathReason
POST /api/v1/customersregistration of a new customer — UC-001's decision, kept by the user in this run. Protected only by Idempotency-Key and its own validation
/v3/api-docs/, /swagger-ui/, /swagger-ui.htmlOpenAPI document and UI — public by the rule's default; the user kept it
/actuator/health/**, /actuator/infoliveness and readiness probes of the platform; details only for OPERATOR

Request rules, specific to general: docs permitAll → health and info permitAll → /actuator/** hasRole(OPERATOR) → POST /api/v1/customers permitAll → DELETE /api/v1/customers/* hasRole(DATA_PROTECTION_OFFICER) → anyRequest().authenticated().

## 2 · Mechanism

DecisionValueWhy
MechanismOAuth2 resource server, signed token (JWT)references/mechanism-decision-matrix.md § 2 row 3: the bank's identity provider issues signed tokens (00-caso-de-uso.md, Access)
Inherited?no — first secured caseno security starter in pom.xml, no chain on disk, no earlier 32-seguranca.md
ExemplarsSecurityConfig.java.example + JwtResourceServerConfig.java.example—

Rejected, one line each: opaque token — the provider issues JWTs, and introspection adds a call per request. Users owned by the application — the bank's provider owns the accounts. API key — no machine caller in this case.

## 3 · Authorities

FactValueSource
Roles in the tokenflat top-level roles array → prefix ROLE_asked in this run
Groups in the tokennot usedno Access names a group
Scopesscope → prefix SCOPE_ (framework default, kept)—
Audience expectedbank-appasked in this run
Principal namesubdecided — the subject is the stable id; no actor travels in this case's command
Role catalogRoles.DATA_PROTECTION_OFFICER, Roles.OPERATOR — NEW, in infrastructure.config.securityfirst roles of the project

## 4 · Project-wide setup

ComponentStateExemplarDecision
SecurityConfig + SecurityAccessPropertiesNEWSecurityConfig.java.examplestateless; CSRF off (bearer token, no cookie authenticates); default headers kept — HSTS included; request rules of § 1; deny by default
JwtResourceServerConfig + TokenAuthoritiesProperties + ClaimPathAuthoritiesConverterNEWJwtResourceServerConfig.java.exampleissuer and audience validated by Boot's issuer-uri + audiences; converter reads roles → ROLE_, scope → SCOPE_; no JwtDecoder bean of our own
ProblemDetailAuthenticationEntryPoint, ProblemDetailAccessDeniedHandlerNEWProblemDetailSecurityHandlers.java.example401 with WWW-Authenticate: Bearer, 403, Problem Details with traceId
SecurityExceptionHandlernot neededSecurityExceptionHandler.java.exampleno method security → no AccessDeniedException reaches MVC; added by the first case with @PreAuthorize
MethodSecurityConfignot neededMethodSecurityAnnotations.java.examplesame reason
RolesNEWMethodSecurityAnnotations.java.example (constants only)DATA_PROTECTION_OFFICER, OPERATOR
SecurityAuditListenerNEWSecurityAuditListener.java.exampleWARN line + security.authentication.failures{reason}, security.authorization.denied{kind}; never the token
OpenApiSecurityConfigNEWOpenApiSecurityConfig.java.examplebearer scheme, required globally; CreateCustomerOpenApiDocs overrides with an empty requirement (public)
HstsHeaderFilterREUSE — becomes inert—@ConditionalOnMissingClass(SecurityFilterChain) drops it when the starter arrives; Spring Security's HstsHeaderWriter is the single writer, same value max-age=31536000 ; includeSubDomains, secure requests only (@.claude/rules/transport-security.md § HSTS). The chain must not call headers().disable() nor httpStrictTransportSecurity().disable()
CORSNEWSecurityConfig.java.exampleorigins from app.security.cors.allowed-origins (CORS_ALLOWED_ORIGINS), empty → no mapping; methods GET, POST, DELETE; headers Authorization, Content-Type, Idempotency-Key; no credentials (bearer, not cookies)
ActuatorCHANGE—/actuator/metrics and health details → OPERATOR (@.claude/rules/authorization.md § Default access; user confirmed); exposure list unchanged (health,info,metrics)

## 5 · Ownership

none — Access is a role, not the owner. No actor travels in AnonymizeCustomerCommand.

## 6 · Failure responses

SituationStatusHeaderBody
no token, malformed, expired, wrong issuer or audience — any protected path401WWW-Authenticate: BearerProblem Details, no reason in detail
token without ROLE_DATA_PROTECTION_OFFICER on anonymizeCustomer403—Problem Details
token without ROLE_OPERATOR on /actuator/metrics or other non-public actuator403—Problem Details

No errorCode on 401/403: they are not domain errors. The 401 and 403 rows of 30-rest.md § 3 are these.

## 7 · Schema requirements

none — the identity provider holds the credentials.

## 8 · Declared dependencies

DependencyVersionWhy
org.springframework.boot:spring-boot-starter-security(managed by the Boot parent)filter chain
org.springframework.boot:spring-boot-starter-security-oauth2-resource-server(managed by the Boot parent)JWT decoding and validation — the Boot 4 name (Initializr id oauth2-resource-server)

The test support (spring-boot-starter-security-test, spring-boot-starter-security-oauth2-resource-server-test) is declared by 40-testes.md § 5.

## 9 · Configuration

PropertyValueSecret?
spring.security.oauth2.resourceserver.jwt.issuer-uri${OAUTH2_ISSUER_URI:http://localhost:8180/realms/bank}no — the default points at the local Keycloak deferred in § 11
spring.security.oauth2.resourceserver.jwt.audiences${OAUTH2_AUDIENCE:bank-app}no
app.security.api-docs-publictrueno
app.security.actuator-roleOPERATORno
app.security.cors.allowed-origins${CORS_ALLOWED_ORIGINS:} — comma-separated, empty by defaultno
app.security.token.principal-claimsubno
app.security.token.roles-claimsrolesno
app.security.token.scopes-claimsscopeno
management.endpoint.health.rolesOPERATOR (show-details: when-authorized already set)no

One spring: root and one app: root in application.yml — merged, never a second root. Shape in templates/application-security.yml.example. OAUTH2_ISSUER_URI, OAUTH2_AUDIENCE and CORS_ALLOWED_ORIGINS go into .env.example with their defaults. With issuer-uri, Boot resolves the provider's keys lazily: the application starts without a reachable provider, and the first protected call answers 401 until one exists. The test profile keeps the chain on and uses the test support's jwt() request post-processor — never a live provider.

## 10 · Contract test cases

EndpointCaseExpected
anonymizeCustomerno token401, WWW-Authenticate present, use case never called
anonymizeCustomertoken with ROLE_OPERATOR only403, use case never called
anonymizeCustomertoken with ROLE_DATA_PROTECTION_OFFICER204
createCustomer (UC-001, impact)no token201 — public proven public
getCustomer (UC-003, impact)no token · any token401 · 200
/actuator/healthno token200
/v3/api-docsno token200
/actuator/metricsno token · token without ROLE_OPERATOR · token with it401 · 403 · 200
CORS preflight OPTIONS /api/v1/customers/{id}Origin listed · Origin not listedallowed headers present · 403
HSTSsecure request (X-Forwarded-Proto: https from a trusted proxy)Strict-Transport-Security: max-age=31536000 ; includeSubDomains, exactly one header
ClaimPathAuthoritiesConvertertoken with roles: ["DATA_PROTECTION_OFFICER"]authority ROLE_DATA_PROTECTION_OFFICER; no roles claim → no role authority

## 11 · Deferred and requirements to other partials

DecidedMissingNormIntended owner
the local issuer default is http://localhost:8180/realms/banka Keycloak service in docker-compose.yml with a bank realm, audience bank-app, a flat roles claim mapper, and test users holding DATA_PROTECTION_OFFICER and OPERATOR — until it exists, a local ./mvnw spring-boot:run answers 401 on every protected call@.claude/rules/authorization.md § Mechanism (a provider the tests never call, a local one for the developer)backlog — user's decision ("local Keycloak later"); consolidation assigns the BL-NN

Requirements to other partials: none — no actor, no credential store.

## Design patterns

none — one claims source per kind of authority, no growing branch over authentication types.

## Impact on approved use cases

CaseEndpointBeforeAfterDecision
UC-001-create-customerPOST /api/v1/customerspublic (no chain existed)public — permitAll with its reasonkept public — asked in this run
UC-001-create-customerCreateCustomerOpenApiDocsno security requirementempty @SecurityRequirements — overrides the global bearer requirementthe document must say the operation is public
UC-003-get-customerGET /api/v1/customers/{customerId}public — accepted risk: anyone with an id read personal dataauthenticated (any valid token, no role)protected from now on — asked in this run. UC-003's accepted risk narrows to any authenticated caller of the bank's provider
UC-003-get-customerGetCustomerOpenApiDocsno 401gains a 401 @ApiResponsethe operation can now answer it
UC-001, UC-003CustomerControllerTestran with no filter chainimports SecurityConfig and JwtResourceServerConfig; getCustomer cases send a jwt(); createCustomer cases send none; new cases of § 10listed for test-architect
allActuator /actuator/metricspublic (no chain existed)OPERATOR only@.claude/rules/authorization.md § Default access, user confirmed

## Implementation order

  1. The dependencies of § 8
  2. Roles, ProblemDetailAuthenticationEntryPoint, ProblemDetailAccessDeniedHandler
  3. JwtResourceServerConfig (+ properties, converter), then SecurityConfig (+ properties), SecurityAuditListener, OpenApiSecurityConfig
  4. OpenAPI changes on CreateCustomerOpenApiDocs and GetCustomerOpenApiDocs — after the REST block
  5. Properties in application.yml, variables in .env.example
  6. Tests — 40-testes.md
Abrir o arquivo completo no GitHub →
§ comportamento

O que lê, escreve e recusa

O contrato da própria skill, do SKILL.md, no texto original.

Class

design — the territory is skill_classes.design in @.claude/schemas/extensions.json, and ArchHook.java guard enforces it. Writes inside the use case folder and nothing else; a SecurityConfig written under src/ is refused with exit 2.

Reads

docs/use-cases/UC-NNN-<slug>/00-use-case.md, 10-domain.md, and 30-rest.md or 31-graphql.md (mandatory — stops without the one the trigger names), earlier cases' 32-security.md, pom.xml (or the Gradle build), src/main/resources/application*.yml, the security wiring on disk, the active blueprint's packages.map, @.claude/rules/authorization.md, @.claude/rules/api-rest.md, @.claude/rules/api-graphql.md (§ Access, when the trigger names GraphQL), @.claude/rules/architecture-ddd.md, @.claude/rules/logging.md, @.claude/rules/observability.md, and references/.

Writes

docs/use-cases/UC-NNN-<slug>/32-security.md. Nothing else.

Owns

the project's authentication mechanism, the claims-to-authorities mapping and the role and group catalog, each endpoint's access rule and where it is enforced, the permitAll list and its reasons, the project-wide filter chain, the 401 and 403 responses, CORS, and the OpenAPI and Actuator exposure.

Does not decide

the use case boundary or its Access fact (00-use-case.md), the actor field, the owner field or the ownership invariant (10-domain.md), endpoints and domain error rows (30-rest.md), tables and columns (20-persistence.md), or the test shape (40-tests.md). Does not write Java, pom.xml or application.yml — the executor does, from this partial. Doesn't touch .claude/rules/**. Does not design reactive (WebFlux) security.

Fonte: SKILL.md § Contract, no nerviz e4cae27.

§ guard e hook

O que o guard faz com ela

design é a classe da skill. O ArchHook abre uma fase quando a skill é chamada e confere cada escrita contra o território da classe. O texto à direita é o do próprio hook.

write_allow da classe design

  • ✓docs/use-cases/UC-*/**
  • ✓docs/use-cases/BACKLOG.md
  • src/**: migrations e classes são do executor
  • docker-compose.yml: arquivo de outra classe
  • caso de uso já aprovado: as specs ficam congeladas

A recusa sai com código 2 e a mensagem volta para o modelo.

PreToolUse  Skill(security-architect)
  audit call                       exit 0
  guard call                       exit 0
# a skill of another class replaces the phase
# write_allow: docs/use-cases/UC-*/**, docs/use-cases/BACKLOG.md
§ orquestração

Onde ela fica no trilho

Cada cartão é um passo do /new-feature; a seta leva o parcial que um passo entrega ao seguinte. Passos tracejados só rodam quando o caso de uso precisa.

/new-feature-implement Quem lê o parcial depois

Depois da aprovação, o /new-feature-implement faz o executor construir o código. A spec é consolidada por referência e vale onde ela e um parcial divergem; o parcial é lido, por seção, para o detalhe.

32-seguranca.mddocs/use-cases/UC-NNN-…/
lido por seção, para o detalhe
java-spring-boot-developeragent · executor
escreve o código
src/**desenha um caso de uso numa spec, não escreve código
← voltar às cadeias