skip to content

What does the hasPermission() expression do inside @PreAuthorize, and what backs it?

level: juniorimportance: should knowfreq 45%

answer

  1. instance-level, not role-level
  2. delegates to PermissionEvaluator SPI
  3. default = DenyAllPermissionEvaluator (always false)
  4. two overloads: (obj,perm) and (id,type,perm)
  5. permission arg is opaque, evaluator interprets it

basics

~10 s

hasPermission() is a SpEL check you write in @PreAuthorize/@PostAuthorize. It asks 'can this user do X on this object?'. Spring delegates the yes/no answer to a PermissionEvaluator bean you configure.

solid answer

~40 s

hasPermission() is a built-in Spring Security SpEL function usable in @PreAuthorize and @PostAuthorize. It has two forms: hasPermission(targetObject, permission) and hasPermission(id, type, permission). Unlike hasRole()/hasAuthority(), which only look at the user's granted authorities, hasPermission() is about a specific domain object — e.g. 'can this user edit *this* document?'. Spring routes the call to a PermissionEvaluator implementation. If you don't register one, the default DenyAllPermissionEvaluator returns false for everything, so the expression always denies. You wire your own by exposing a MethodSecurityExpressionHandler bean with setPermissionEvaluator(...). The 'permission' argument is arbitrary — often a string like 'READ'/'WRITE' or an ACL Permission mask — and it is your evaluator that interprets it.

code

java · 15 lines
java
@Service
public class DocumentService {

    // 'can this user WRITE this specific document?'
    @PreAuthorize("hasPermission(#doc, 'WRITE')")
    public void update(Document doc) { /* ... */ }

    // id-based overload: object not loaded yet
    @PreAuthorize("hasPermission(#id, 'com.acme.Document', 'READ')")
    public Document load(Long id) { /* ... */ }

    // check AFTER the method runs, against the returned object
    @PostAuthorize("hasPermission(returnObject, 'READ')")
    public Document findConfidential(Long id) { /* ... */ }
}

go deeper

for a junior

Know it's an object-level check delegating to a PermissionEvaluator, distinct from hasRole.

for a middle

Know the two overloads, the deny-by-default behaviour, and that the permission arg is opaque.

for a senior

Explain the SecurityExpressionRoot delegation path and @PostAuthorize returnObject semantics.

for a principal

Weigh per-object evaluation cost and where instance-level checks belong versus role checks.

## The problem it solves Role checks answer *what a user is* (`hasRole('ADMIN')`). But often you need *what a user can do to a specific thing*: 'user 42 may edit document 99 but not document 100'. That is **domain-object** (instance-level) authorization, and roles cannot express it. Spring Security exposes it through the **`hasPermission()`** SpEL function. ## Where you write it Inside method-security annotations, enabled by `@EnableMethodSecurity` (Spring Security 6; the older `@EnableGlobalMethodSecurity` did the same in 5.x): ```java @PreAuthorize("hasPermission(#doc, 'WRITE')") void update(Document doc) { ... } @PostAuthorize("hasPermission(returnObject, 'READ')") Document load(Long id) { ... } ``` `#doc` references a method argument; `returnObject` references the value returned (only in `@PostAuthorize`). There are **two overloads** of the expression: - `hasPermission(Object target, Object permission)` — you already hold the object. - `hasPermission(Serializable id, String type, Object permission)` — you only have an id + a type name, so the object need not be loaded. ## What actually runs The SpEL root object is a `MethodSecurityExpressionOperations` (implemented by `SecurityExpressionRoot`). Its `hasPermission(...)` method delegates to a **`PermissionEvaluator`** — the SPI (service provider interface): ```java public interface PermissionEvaluator { boolean hasPermission(Authentication a, Object targetDomainObject, Object permission); boolean hasPermission(Authentication a, Serializable targetId, String targetType, Object permission); } ``` The `Authentication` (current user) is supplied automatically; you never pass it in SpEL. ## The default is deny If you never register a `PermissionEvaluator`, Spring uses **`DenyAllPermissionEvaluator`**, whose methods always return `false`. So an unconfigured `hasPermission(...)` **always denies** — a classic 'why is everything 403?' gotcha. You register your own by publishing a `MethodSecurityExpressionHandler` bean (see the wiring question). ## The permission argument is opaque The third argument (`'WRITE'`, `'READ'`, an integer mask, an enum…) means nothing to Spring itself — **your** `PermissionEvaluator` interprets it. The ACL module's `AclPermissionEvaluator` maps strings like `'READ'` to `BasePermission.READ` bitmasks, but a hand-written evaluator can treat it however it likes. ## When to use it Use `hasPermission()` when authorization depends on *which instance* is being touched and the rule can't be reduced to a role. For coarse, role-only rules, prefer `hasRole`/`hasAuthority` — they don't need a database lookup per object.

  • Why might every hasPermission() check return 403 in a fresh app?
    Because no PermissionEvaluator is registered, so the DenyAllPermissionEvaluator default is used and denies everything. You must publish a MethodSecurityExpressionHandler bean with your evaluator.
  • How does the two-argument overload differ from the three-argument one?
    Two-arg takes the already-loaded domain object; three-arg takes an id + type string so you can authorize without loading the object first — cheaper when you only have the identifier.

saying these in an interview costs you the question

  • Thinking hasPermission() checks the user's roles/authorities — it checks a domain object via a PermissionEvaluator.
  • Assuming hasPermission() works out of the box without registering an evaluator.
  • Believing the permission string ('READ'/'WRITE') has built-in meaning to Spring rather than being interpreted by your evaluator.

context