or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

aot-native-support.mdauthentication-core.mdauthentication-events.mdauthentication-management.mdauthentication-tokens.mdauthorities.mdauthorization.mdcompromised-password.mdconcurrent-async.mddao-authentication.mdexpression-access-control.mdindex.mdjaas-authentication.mdjackson-serialization.mdmethod-security.mdobservation-metrics.mdone-time-tokens.mdprovisioning.mdsecurity-context.mdsession-management.mduser-details.md
tile.json

expression-access-control.mddocs/

SpEL-Based Expression Access Control

Spring Security provides powerful expression-based access control using Spring Expression Language (SpEL). This enables fine-grained authorization decisions using dynamic expressions evaluated at runtime.

Key Information for Agents

Core Capabilities:

  • SpEL expression evaluation for authorization decisions
  • SecurityExpressionHandler<T> creates and evaluates security expressions
  • SecurityExpressionOperations provides methods available in expressions (hasRole, hasAuthority, etc.)
  • Method security expressions via MethodSecurityExpressionHandler with parameter access
  • Permission evaluation via PermissionEvaluator for domain object permissions
  • Expression root objects: authentication, principal, filterObject, returnObject

Key Interfaces and Classes:

  • SecurityExpressionHandler<T> - Interface: getExpressionParser(), createEvaluationContext()
  • AbstractSecurityExpressionHandler<T> - Base implementation with configurable components
  • SecurityExpressionOperations - Interface defining expression methods (hasRole, hasAuthority, etc.)
  • SecurityExpressionRoot - Base implementation of expression operations
  • MethodSecurityExpressionHandler - Specialized for method security (parameter access)
  • DefaultMethodSecurityExpressionHandler - Default implementation for method security
  • MethodSecurityExpressionOperations - Extended operations for method security
  • PermissionEvaluator - Interface for domain object permission checks

Default Behaviors:

  • Expression parser: SpelExpressionParser by default
  • Role prefix: "ROLE_" by default (configurable via setDefaultRolePrefix())
  • Permission evaluator: DenyAllPermissionEvaluator by default (denies all)
  • Parameter name discovery: Required for #paramName syntax (set via setParameterNameDiscoverer())
  • Expression evaluation: Returns boolean (true = granted, false = denied)

Threading Model:

  • Expression handlers are stateless (thread-safe)
  • Evaluation contexts created per invocation (not shared)

Lifecycle:

  • Expression handlers configured as beans
  • Components set via setter methods (permission evaluator, role hierarchy, etc.)

Exceptions:

  • Expression evaluation errors: May throw SpelEvaluationException (handle gracefully)
  • Permission evaluation: Returns boolean (no exceptions thrown)

Edge Cases:

  • Null authentication: Most expressions deny if authentication is null
  • Null return value: @PostAuthorize expressions must handle null returnObject
  • Parameter access: Requires parameter name discovery (use DefaultParameterNameDiscoverer)
  • Custom methods: Extend MethodSecurityExpressionRoot to add custom expression methods
  • Permission evaluation: Two overloads - object-based and ID+type-based
  • Expression caching: Expressions parsed once and cached (efficient)

Core Interfaces

SecurityExpressionHandler

Base interface for creating and evaluating security expressions.

package org.springframework.security.access.expression;

interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
    ExpressionParser getExpressionParser();
    EvaluationContext createEvaluationContext(
        Authentication authentication,
        T invocation);
}

Key Methods:

ExpressionParser getExpressionParser()
  • Returns the SpEL expression parser
  • Used to parse string expressions into Expression objects
EvaluationContext createEvaluationContext(
    Authentication authentication,
    T invocation)
  • Creates evaluation context for expression evaluation
  • Parameters:
    • authentication - The current authentication
    • invocation - The secured object
  • Returns: EvaluationContext with security variables and functions

AbstractSecurityExpressionHandler

Abstract base implementation providing common functionality.

package org.springframework.security.access.expression;

abstract class AbstractSecurityExpressionHandler<T>
    implements SecurityExpressionHandler<T> {
    AbstractSecurityExpressionHandler();

    void setPermissionEvaluator(PermissionEvaluator permissionEvaluator);
    void setRoleHierarchy(RoleHierarchy roleHierarchy);
    void setDefaultRolePrefix(String defaultRolePrefix);
    ExpressionParser getExpressionParser();
}

Key Methods:

void setPermissionEvaluator(PermissionEvaluator permissionEvaluator)
  • Sets the permission evaluator for hasPermission() expressions
void setRoleHierarchy(RoleHierarchy roleHierarchy)
  • Sets role hierarchy for hierarchical role checks
void setDefaultRolePrefix(String defaultRolePrefix)
  • Sets default prefix for role names (default: "ROLE_")

Example:

@Bean
public DefaultMethodSecurityExpressionHandler expressionHandler() {
    DefaultMethodSecurityExpressionHandler handler =
        new DefaultMethodSecurityExpressionHandler();
    handler.setPermissionEvaluator(customPermissionEvaluator());
    handler.setRoleHierarchy(roleHierarchy());
    handler.setDefaultRolePrefix("ROLE_");
    return handler;
}

SecurityExpressionOperations

Interface defining available operations in security expressions.

package org.springframework.security.access.expression;

interface SecurityExpressionOperations {
    Authentication getAuthentication();
    Object getPrincipal();

    boolean hasAuthority(String authority);
    boolean hasAnyAuthority(String... authorities);
    boolean hasRole(String role);
    boolean hasAnyRole(String... roles);

    boolean isAnonymous();
    boolean isAuthenticated();
    boolean isRememberMe();
    boolean isFullyAuthenticated();

    boolean permitAll();
    boolean denyAll();

    boolean hasPermission(Object target, Object permission);
    boolean hasPermission(Object targetId, String targetType, Object permission);

    Object getFilterObject();
    Object getReturnObject();
    Object getThis();
}

Authentication Access:

Authentication getAuthentication()
  • Returns current authentication
Object getPrincipal()
  • Returns authentication principal

Authority Checks:

boolean hasAuthority(String authority)
  • Checks if authentication has specific authority
boolean hasAnyAuthority(String... authorities)
  • Checks if authentication has any of the specified authorities
boolean hasRole(String role)
  • Checks if authentication has specific role (adds default prefix)
boolean hasAnyRole(String... roles)
  • Checks if authentication has any of the specified roles

Authentication State:

boolean isAnonymous()
  • Returns true if authentication is anonymous
boolean isAuthenticated()
  • Returns true if authentication is authenticated (not anonymous)
boolean isRememberMe()
  • Returns true if authentication is remember-me
boolean isFullyAuthenticated()
  • Returns true if authenticated and not anonymous or remember-me

Universal Access:

boolean permitAll()
  • Always returns true (allows all access)
boolean denyAll()
  • Always returns false (denies all access)

Permission Checks:

boolean hasPermission(Object target, Object permission)
  • Checks permission on target object
boolean hasPermission(Object targetId, String targetType, Object permission)
  • Checks permission on object identified by ID and type

Context Objects:

Object getFilterObject()
  • Returns object being filtered (for @PreFilter/@PostFilter)
Object getReturnObject()
  • Returns method return value (for @PostAuthorize)
Object getThis()
  • Returns target object of method invocation

SecurityExpressionRoot

Abstract base implementation of SecurityExpressionOperations.

package org.springframework.security.access.expression;

abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
    SecurityExpressionRoot(Authentication authentication);
}

Constructor:

SecurityExpressionRoot(Authentication authentication)

Method Security Expressions

MethodSecurityExpressionHandler

Expression handler specialized for method security.

package org.springframework.security.access.expression.method;

interface MethodSecurityExpressionHandler
    extends SecurityExpressionHandler<MethodInvocation> {
    Object filter(
        Object filterTarget,
        Expression filterExpression,
        EvaluationContext ctx);
    void setReturnObject(Object returnObject, EvaluationContext ctx);
}

Key Methods:

Object filter(
    Object filterTarget,
    Expression filterExpression,
    EvaluationContext ctx)
  • Filters collection or array based on expression
void setReturnObject(Object returnObject, EvaluationContext ctx)
  • Sets return object in evaluation context for @PostAuthorize

DefaultMethodSecurityExpressionHandler

Default implementation for method security expressions.

package org.springframework.security.access.expression.method;

class DefaultMethodSecurityExpressionHandler
    extends AbstractSecurityExpressionHandler<MethodInvocation>
    implements MethodSecurityExpressionHandler {
    DefaultMethodSecurityExpressionHandler();

    void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer);
    void setTrustResolver(AuthenticationTrustResolver trustResolver);
    EvaluationContext createEvaluationContext(
        Authentication auth,
        MethodInvocation mi);
}

Key Methods:

void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer)
  • Sets parameter name discoverer for accessing method parameters by name
void setTrustResolver(AuthenticationTrustResolver trustResolver)
  • Sets trust resolver for isAnonymous(), isRememberMe() checks

Example:

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {

    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler =
            new DefaultMethodSecurityExpressionHandler();

        handler.setPermissionEvaluator(permissionEvaluator());
        handler.setRoleHierarchy(roleHierarchy());
        handler.setDefaultRolePrefix("ROLE_");

        // Enable parameter name discovery
        handler.setParameterNameDiscoverer(
            new DefaultParameterNameDiscoverer()
        );

        return handler;
    }

    @Bean
    public PermissionEvaluator permissionEvaluator() {
        return new CustomPermissionEvaluator();
    }

    @Bean
    public RoleHierarchy roleHierarchy() {
        return RoleHierarchyImpl.withDefaultRolePrefix()
            .role("ADMIN").implies("USER")
            .role("USER").implies("GUEST")
            .build();
    }
}

MethodSecurityExpressionOperations

Extended operations interface for method security.

package org.springframework.security.access.expression.method;

interface MethodSecurityExpressionOperations
    extends SecurityExpressionOperations {
}

MethodSecurityExpressionRoot

Root object for method security expressions.

package org.springframework.security.access.expression.method;

class MethodSecurityExpressionRoot extends SecurityExpressionRoot
    implements MethodSecurityExpressionOperations {
    MethodSecurityExpressionRoot(
        Authentication authentication,
        MethodInvocation methodInvocation);
}

Constructor:

MethodSecurityExpressionRoot(
    Authentication authentication,
    MethodInvocation methodInvocation)

Expression Usage

Pre/Post Authorization Annotations

@PreAuthorize

Evaluates expression before method invocation.

@PreAuthorize("hasRole('ADMIN')")
public void adminOnly() {
    // Only users with ROLE_ADMIN can execute
}

@PreAuthorize("hasAuthority('READ_PRIVILEGE')")
public Document readDocument(Long id) {
    return documentRepository.findById(id);
}

@PreAuthorize("isAuthenticated()")
public void authenticatedOnly() {
    // Any authenticated user can execute
}

@PreAuthorize("#username == authentication.name")
public User getUser(String username) {
    // Users can only access their own data
    return userRepository.findByUsername(username);
}

@PreAuthorize("hasPermission(#document, 'read')")
public void processDocument(Document document) {
    // Check permission on domain object
}

@PostAuthorize

Evaluates expression after method invocation with access to return value.

@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) {
    // Method executes, then checks if returned document
    // belongs to current user
    return documentRepository.findById(id);
}

@PostAuthorize("hasPermission(returnObject, 'read')")
public Project getProject(Long id) {
    Project project = projectRepository.findById(id);
    // Checks permission on returned project
    return project;
}

@PreFilter

Filters method parameter collection/array before invocation.

@PreFilter("hasPermission(filterObject, 'delete')")
public void deleteDocuments(List<Document> documents) {
    // Only documents user has 'delete' permission on are passed
    documentRepository.deleteAll(documents);
}

@PreFilter(value = "filterObject.owner == authentication.name",
           filterTarget = "documents")
public void processDocuments(List<Document> documents, String action) {
    // Filter specific parameter when multiple collections
}

@PostFilter

Filters method return collection/array after invocation.

@PostFilter("hasPermission(filterObject, 'read')")
public List<Document> getAllDocuments() {
    List<Document> all = documentRepository.findAll();
    // Returns only documents user can read
    return all;
}

@PostFilter("filterObject.owner == authentication.name")
public List<Project> getProjects() {
    // Returns only projects owned by current user
    return projectRepository.findAll();
}

Common Expression Patterns

Role-Based Access

@PreAuthorize("hasRole('ADMIN')")
public void adminFunction() { }

@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public void managerOrAdminFunction() { }

// With role hierarchy configured
@PreAuthorize("hasRole('USER')") // Admins also have access if hierarchy configured
public void userFunction() { }

Authority-Based Access

@PreAuthorize("hasAuthority('WRITE_PRIVILEGE')")
public void writeData() { }

@PreAuthorize("hasAnyAuthority('READ_PRIVILEGE', 'WRITE_PRIVILEGE')")
public void accessData() { }

Authentication State

@PreAuthorize("isAuthenticated()")
public void authenticatedUsers() { }

@PreAuthorize("isAnonymous()")
public void anonymousOnly() { }

@PreAuthorize("isFullyAuthenticated()") // Not remember-me
public void sensitiveOperation() { }

@PreAuthorize("permitAll()")
public void publicAccess() { }

@PreAuthorize("denyAll()")
public void neverAccessible() { }

Parameter-Based Access

// Access method parameter by name
@PreAuthorize("#username == authentication.name")
public void updateUser(String username, UserDto updates) { }

// Access parameter properties
@PreAuthorize("#user.id == authentication.principal.id")
public void updateUser(@P("user") User user) { }

// Complex expressions with parameters
@PreAuthorize("#id == authentication.principal.id or hasRole('ADMIN')")
public void deleteUser(Long id) { }

Return Value Access

// Check return value property
@PostAuthorize("returnObject.createdBy == authentication.name")
public Report getReport(Long id) { }

// Check multiple conditions on return value
@PostAuthorize("returnObject != null and returnObject.status == 'PUBLISHED'")
public Article getArticle(Long id) { }

Permission-Based Access

// Permission on parameter
@PreAuthorize("hasPermission(#document, 'write')")
public void updateDocument(Document document) { }

// Permission by ID and type
@PreAuthorize("hasPermission(#id, 'com.example.Document', 'delete')")
public void deleteDocument(Long id) { }

// Permission on return value
@PostAuthorize("hasPermission(returnObject, 'read')")
public Document getDocument(Long id) { }

Combining Expressions

// AND conditions
@PreAuthorize("hasRole('ADMIN') and #user.department == authentication.principal.department")
public void updateUser(User user) { }

// OR conditions
@PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')")
public void viewProfile(Long userId) { }

// Complex conditions
@PreAuthorize("(hasRole('MANAGER') and #report.department == authentication.principal.department) " +
              "or hasRole('ADMIN')")
public void approveReport(Report report) { }

Custom Expression Methods

Extend MethodSecurityExpressionRoot to add custom methods:

public class CustomMethodSecurityExpressionRoot
        extends MethodSecurityExpressionRoot {

    public CustomMethodSecurityExpressionRoot(
            Authentication authentication, MethodInvocation methodInvocation) {
        super(authentication, methodInvocation);
    }

    // Custom expression method
    public boolean isOwner(Object object) {
        if (object instanceof Ownable) {
            Ownable ownable = (Ownable) object;
            String currentUser = getAuthentication().getName();
            return ownable.getOwner().equals(currentUser);
        }
        return false;
    }

    // Custom method with additional logic
    public boolean isSameDepartment(Long userId) {
        User currentUser = (User) getAuthentication().getPrincipal();
        UserService userService = getBean(UserService.class);
        User targetUser = userService.findById(userId);
        return currentUser.getDepartment()
            .equals(targetUser.getDepartment());
    }
}

// Custom expression handler
public class CustomMethodSecurityExpressionHandler
        extends DefaultMethodSecurityExpressionHandler {

    private ApplicationContext applicationContext;

    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
            Authentication authentication, MethodInvocation invocation) {
        CustomMethodSecurityExpressionRoot root =
            new CustomMethodSecurityExpressionRoot(authentication, invocation);
        root.setApplicationContext(applicationContext);
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(getTrustResolver());
        root.setRoleHierarchy(getRoleHierarchy());
        return root;
    }

    public void setApplicationContext(ApplicationContext context) {
        this.applicationContext = context;
    }
}

// Configuration
@Bean
public MethodSecurityExpressionHandler expressionHandler(
        ApplicationContext context) {
    CustomMethodSecurityExpressionHandler handler =
        new CustomMethodSecurityExpressionHandler();
    handler.setApplicationContext(context);
    return handler;
}

// Usage
@PreAuthorize("isOwner(#document)")
public void updateDocument(Document document) { }

@PreAuthorize("isSameDepartment(#userId)")
public void shareData(Long userId) { }

Permission Evaluation

PermissionEvaluator

Interface for evaluating permissions on domain objects.

package org.springframework.security.access;

interface PermissionEvaluator extends AopInfrastructureBean {
    boolean hasPermission(
        Authentication authentication,
        Object targetDomainObject,
        Object permission);
    boolean hasPermission(
        Authentication authentication,
        Serializable targetId,
        String targetType,
        Object permission);
}

Methods:

boolean hasPermission(
    Authentication authentication,
    Object targetDomainObject,
    Object permission)
  • Evaluates permission on domain object
boolean hasPermission(
    Authentication authentication,
    Serializable targetId,
    String targetType,
    Object permission)
  • Evaluates permission on object by ID and type

Example:

public class CustomPermissionEvaluator implements PermissionEvaluator {

    private final PermissionRepository permissionRepository;

    @Override
    public boolean hasPermission(Authentication authentication,
                                 Object targetDomainObject,
                                 Object permission) {
        if (authentication == null || targetDomainObject == null) {
            return false;
        }

        String username = authentication.getName();
        String permissionName = permission.toString();

        // Check permission based on domain object
        if (targetDomainObject instanceof Document) {
            Document doc = (Document) targetDomainObject;
            return checkDocumentPermission(username, doc, permissionName);
        }

        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication,
                                 Serializable targetId,
                                 String targetType,
                                 Object permission) {
        if (authentication == null || targetId == null) {
            return false;
        }

        String username = authentication.getName();
        return permissionRepository.hasPermission(
            username, targetId, targetType, permission.toString()
        );
    }

    private boolean checkDocumentPermission(
            String username, Document doc, String permission) {
        // Custom permission logic
        if ("read".equals(permission)) {
            return doc.isPublic() || doc.getOwner().equals(username) ||
                   doc.getReaders().contains(username);
        }
        if ("write".equals(permission)) {
            return doc.getOwner().equals(username) ||
                   doc.getEditors().contains(username);
        }
        return false;
    }
}

DenyAllPermissionEvaluator

Permission evaluator that denies all permissions.

package org.springframework.security.access.expression;

class DenyAllPermissionEvaluator implements PermissionEvaluator {
    DenyAllPermissionEvaluator();
}

Description: Default implementation that always returns false.

Expression Utilities

ExpressionUtils

Utility class for expression evaluation.

package org.springframework.security.access.expression;

class ExpressionUtils {
    static boolean evaluateAsBoolean(
        Expression expr,
        EvaluationContext ctx);
}

Static Methods:

static boolean evaluateAsBoolean(
    Expression expr,
    EvaluationContext ctx)
  • Evaluates expression as boolean

Best Practices

  1. Keep expressions simple - Complex logic should be in custom methods
  2. Use parameter names - Enable parameter name discovery for readable expressions
  3. Implement PermissionEvaluator - For domain-specific permission checks
  4. Configure role hierarchy - Simplify role-based expressions
  5. Use @PostAuthorize sparingly - Method executes before check
  6. Filter large collections efficiently - Consider performance of @PreFilter/@PostFilter
  7. Test expressions thoroughly - Security expressions need comprehensive testing
  8. Document custom methods - Make custom expression methods discoverable

Package

  • org.springframework.security.access.expression
  • org.springframework.security.access.expression.method
  • org.springframework.security.access