Authorization Interview Questions & Answers
Authorization in Spring Security — URL authorization rules, request matchers, method security with @PreAuthorize/@PostAuthorize, @Secured, SpEL access expressions, role hierarchies, the AccessDeniedException 403, and securing service vs web layers.
A request matcher selects which requests a rule applies to — by path pattern, HTTP method, or
both. requestMatchers(...) accepts Ant-style patterns and an optional HttpMethod.
auth.requestMatchers("/api/orders/**").hasRole("USER") // any method, path subtree
.requestMatchers(HttpMethod.DELETE, "/api/orders/*").hasRole("ADMIN") // method-specific
.requestMatchers("/files/{name:.+}").permitAll(); // path variable
* matches one path segment, ** matches across segments. Method-specific matchers let you allow
reads but restrict writes on the same path. Modern Spring Security resolves the matcher type
(MVC/servlet) automatically.
Rule of thumb: Use requestMatchers with Ant patterns and optional methods to scope rules — *
is one segment, ** is many, and method matchers separate read from write access.
Add @EnableMethodSecurity to a configuration class. This activates the annotation-based checks —
@PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter — which Spring enforces via an AOP proxy
around the bean.
@Configuration
@EnableMethodSecurity // prePostEnabled = true by default; secured/jsr250 optional
class MethodSecurityConfig { }
@Service
class ReportService {
@PreAuthorize("hasRole('ANALYST')")
Report generate(Long id) { ... }
}
Because it's proxy-based, the same caveats as @Transactional apply: the method must be public
and called from another bean (self-invocation bypasses the check). @EnableMethodSecurity replaces
the older @EnableGlobalMethodSecurity.
Rule of thumb: Turn on @EnableMethodSecurity, then annotate public service methods — it's
proxy-based, so self-invocation skips the check.
@PreAuthorize expressions use built-ins like hasRole, hasAuthority, hasAnyRole,
isAuthenticated(), permitAll, plus the authentication and principal objects and method
arguments via #paramName.
@PreAuthorize("hasRole('ADMIN') or #account.owner == principal.username")
void update(Account account) { ... }
@PreAuthorize("@accessChecker.canEdit(#id, authentication)") // call a bean
void edit(Long id) { ... }
Referencing a @bean method (@accessChecker.canEdit(...)) lets you move non-trivial decisions into
testable Java instead of cramming logic into a string. Keep expressions short; push complexity into a
bean.
Rule of thumb: SpEL gives roles, the principal, and method args in one expression — and for real
logic, delegate to a @bean method rather than writing a long expression.
They filter collections instead of allowing/denying the whole call. @PreFilter removes
disallowed elements from a collection argument before the method runs; @PostFilter removes
elements from the returned collection, using filterObject for the current element.
@PostFilter("filterObject.owner == authentication.name")
List<Document> myDocuments() { return repo.findAll(); } // keeps only the caller's docs
@PreFilter("filterObject.amount < 10000")
void process(List<Payment> payments) { ... } // drops large payments before processing
@PostFilter is convenient but loads everything then discards — for large datasets, filter in the
query instead. Reserve these for small in-memory collections.
Rule of thumb: @PreFilter/@PostFilter prune collections element-by-element; great for small
lists, but for big result sets filter in the database query, not after.
When an authenticated user lacks permission, Spring throws AccessDeniedException, the
ExceptionTranslationFilter catches it, and the response becomes 403 Forbidden (vs 401 for
unauthenticated). You customize it with an AccessDeniedHandler or an exception handler.
http.exceptionHandling(e -> e
.accessDeniedHandler((req, res, ex) ->
res.sendError(403, "Not allowed"))
.authenticationEntryPoint((req, res, ex) ->
res.sendError(401, "Authenticate first")));
401 means "we don't know who you are — log in"; 403 means "we know you, but you may not." For
method-security denials in MVC you can also map AccessDeniedException in a @ControllerAdvice.
Rule of thumb: Authenticated-but-denied → 403 (AccessDeniedHandler); unauthenticated → 401
(AuthenticationEntryPoint) — don't conflate the two.
A RoleHierarchy lets a higher role imply lower ones, so an ADMIN automatically satisfies
hasRole('USER') without granting both explicitly.
@Bean
RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.fromHierarchy(
"ROLE_ADMIN > ROLE_MANAGER \n ROLE_MANAGER > ROLE_USER");
}
Wire it into method security (via a MethodSecurityExpressionHandler) or web authorization. Without a
hierarchy you'd write hasAnyRole('ADMIN','MANAGER','USER') everywhere; the hierarchy keeps rules
simple and consistent.
Rule of thumb: Define a RoleHierarchy so senior roles inherit junior permissions — it removes
repetitive hasAnyRole lists across the app.
Both — they're complementary layers of defense. URL rules are a coarse, centralized gate at the edge; method security puts fine-grained checks next to the business logic, so an endpoint that forgets a URL rule is still protected.
// URL layer: broad gate
auth.requestMatchers("/admin/**").hasRole("ADMIN");
// Method layer: precise, logic-adjacent
@PreAuthorize("hasRole('ADMIN')") void deleteUser(Long id) { ... }
Method security also protects code reached by non-HTTP paths (scheduled jobs, messaging) that URL rules never see. Defense in depth means a single misconfiguration doesn't expose the operation.
Rule of thumb: Use URL rules for edge gatekeeping and method security for logic-adjacent, transport-independent checks — layer them, don't choose one.
Service methods represent the actual business operations and are reachable from many entry points — REST controllers, GraphQL, message listeners, scheduled tasks. Securing there guarantees the rule holds regardless of how the method is invoked.
@Service
class AccountService {
@PreAuthorize("hasRole('ADMIN')")
void closeAccount(Long id) { ... } // enforced from controller, job, or listener alike
}
Securing only the controller leaves the operation exposed if another caller invokes the service directly. The controller still benefits from coarse URL rules, but the authoritative check belongs on the service.
Rule of thumb: Put the authoritative authorization check on the service method — it's the single chokepoint every caller passes through.
For "can this user edit this specific record" decisions, use hasPermission(...) backed by a
custom PermissionEvaluator, or delegate to a bean method in the expression. This moves
instance-level logic out of the annotation.
@PreAuthorize("hasPermission(#id, 'Document', 'EDIT')")
void edit(Long id) { ... }
@Component
class DocumentPermissionEvaluator implements PermissionEvaluator {
public boolean hasPermission(Authentication a, Serializable id, String type, Object perm) {
return docRepo.isOwner((Long) id, a.getName()); // your domain rule
}
// ...
}
Spring's full ACL module exists for rich per-object permissions but is heavyweight; a custom
PermissionEvaluator or a @bean check covers most needs.
Rule of thumb: For per-record access, use hasPermission with a custom PermissionEvaluator (or a
@bean method) instead of stuffing ownership logic into URL rules.
More Security interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.