A Spring Boot web request can pass through servlet filters, route mapping, controller conversion, method security, and repository access. Those layers answer different questions. A security filter chain decides whether a request is admitted and how the caller is authenticated. A role or authority may admit a reviewer to the permit API, but it does not grant access to every permit ID. Object permission belongs near the service operation that reads or writes the record, with current assignment and tenant scope checked against the exact ID. Method security can intercept Spring-managed bean calls when enabled; a direct self-call within the same bean can bypass proxy advice. A menu that hides a case is presentation, not authorization. The response must project only approved fields after access succeeds.
Spring Boot Security Chain and Object Permission
Working case
Reviewer 47 may inspect permit 62; reviewer 81 has an account and the same broad reviewer role but is assigned elsewhere. A filter rule that requires the reviewer role lets both reach GET /permits/62. If the controller calls repository.findById(62) and returns an entity, reviewer 81 receives the private district note. A second version adds a method annotation to a private helper invoked from the same service instance and assumes it ran; the proxy never sees that internal call. The corrected flow authenticates the route, calls a distinct Spring-managed service method protected by method security or an explicit guard, checks assignment for permit 62, and returns a narrow PermitView. A later approval must repeat current-state permission inside the write transaction because assignment may change after the GET.
Implementation boundary
@Configuration
@EnableMethodSecurity
class PermitMethodSecurity { }
@Service
class PermitViewService {
private final PermitCaseRepository permits;
PermitViewService(PermitCaseRepository permits) {
this.permits = permits;
}
@PreAuthorize("@permitAccess.canRead(authentication, #p0)")
public PermitView read(Long permitId) {
PermitCase permit = permits.findById(permitId).orElseThrow();
return new PermitView(permit.getId(), permit.getTitle(), permit.getStatus());
}
}Declare one ordered filter-chain policy for public health and private permit routes, and test the matcher order with both anonymous and authenticated requests. Enable method security explicitly before relying on @PreAuthorize. Place authorization on a public method reached through an injected Spring bean, or perform a direct guard inside the service; do not depend on proxy interception of self-invocation. For list pages, constrain reviewer or tenant ownership in the repository query before paging rather than filtering entities in Java after a broad query. For a single record, decide whether unknown and forbidden IDs share a public response to avoid revealing existence. An authority check, a case assignment query, and a response projection should be visible as separate operations in tests and trace logs.
Cost and boundaries
A precise permission query adds a database predicate or indexed assignment existence check. Its cost is small compared with leaking another reviewer's case, but N separate checks across an unbounded list can create N extra database calls. Put list ownership in SQL and measure the actual plan. A service guard may acquire a second read if the controller already loaded the entity; pass a scoped lookup or keep the fetch and check in one service method to avoid redundant work. Method interception adds a small proxy call overhead, while a deeply nested expression can become hard to audit. Keep security logs free of case bodies and bearer tokens. Record denied route counts, denied object counts, query count, and latency for permitted versus rejected operations.
Failure trace
Request the same permit as reviewer 47, reviewer 81, an anonymous caller, and a user whose assignment was revoked after the page loaded. Confirm route admission and object denial are distinguished internally while public responses follow the chosen existence policy. Invoke the service method from a controller and through a self-call test; the self-call must not be treated as protected merely because an annotation appears on it. Fetch a list of 47 cases and inspect SQL to ensure unauthorized records are excluded before pagination. Seed a private field in permit 62 and verify it never appears in the response. Exercise a future route introduced under the same URL prefix and ensure the filter matcher does not accidentally admit it without a service permission check.
Verification
- Method security is enabled before annotations are relied upon.
- Reviewer 81 cannot read permit 62 despite holding a reviewer role.
- The public view omits private entity fields.
Practice drill
Build a permit detail endpoint for two reviewers and one private case. Configure public health access separately from reviewer routes. Enable method security and put a case-specific guard on the service method actually called by the controller. Test GET detail, list paging, and approval as both reviewers; revoke the assignment between list and approval. Compare a proxy-reached method with a self-invoked helper to make the interception boundary visible. Capture query counts and the serialized response keys. Document which layer authenticates, which layer authorizes the record, and which layer shapes the public result. Reject any implementation that treats a reviewer role as a grant to every permit.
Decision note
The filter chain establishes caller and route admission; a Spring-managed service checks current permission for the exact object.
Common Mistakes
- Treating a route role as authorization for every record.
- Expecting a same-bean self-call to pass through method-security proxy advice.
- Returning a JPA entity directly after a broad permission check.
Related lessons
Spring Boot Web Service Boundaries; Spring Boot MVC Validation and Problem Contracts; Spring Boot JPA Scope, Fetch, and Page Cost; Spring Boot Transaction Replay and Outbox Handoff; Spring Security filter chain: authentication, CSRF and request order; Spring method security: authorization advice runs through the bean proxy; Spring method authorization: test the proxied service boundary; Authorization and Tenant Boundaries.
Apply and check
Build Project: Spring Boot Permit Service Boundaries and review Web Development: Spring Boot service contracts.
