Mastering Dynamic Permission Checks in Spring Boot with SpEL

This guide explains how to replace static custom‑annotation permission checks in Spring Boot with flexible SpEL expressions, covering annotation design, aspect definition, expression parsing, context setup, and practical usage examples for various access scenarios.

Architect's Guide
Architect's Guide
Architect's Guide
Mastering Dynamic Permission Checks in Spring Boot with SpEL

Overview

Traditional permission control in Spring Boot often relies on a custom annotation combined with an aspect that contains hard‑coded logic. While this works for simple cases, real‑world applications require many different access rules such as role‑based, time‑based, or conditional permissions, which quickly become cumbersome to implement with static checks.

Introducing SpEL for Flexible Permission Expressions

Spring Expression Language (SpEL) allows runtime evaluation of expressions and can be used to make permission annotations dynamic. By storing an expression string in the annotation, developers can delegate the actual permission logic to a dedicated evaluator.

Custom annotation definition

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuth {
    /**
     * Example expressions:
     * permissionAll()          – any configured role can access
     * hasPermission("MENU.QUERY") – role with MENU.QUERY permission can access
     * permitAll()              – allow all requests
     * denyAll()                – only super‑admin can access
     * hasAuth()                – must be logged in
     * hasTimeAuth(1,10)        – access allowed between 1‑10 o'clock
     * hasRole('Admin')         – user must have the Admin role
     * hasAllRole('Admin','ChiefEngineer') – user must have both roles
     */
    String value();
}

Aspect definition

@Around("@annotation(com.example.PreAuth) || @within(com.example.PreAuth)")
public Object preAuth(ProceedingJoinPoint point) throws Throwable {
    if (handleAuth(point)) {
        return point.proceed();
    }
    throw new SecureException(ResultCode.REQ_REJECT);
}

private boolean handleAuth(ProceedingJoinPoint point) {
    // TODO: implement actual logic
    return false;
}

The aspect uses @Around to intercept methods or classes annotated with @PreAuth. It extracts the expression from the annotation, parses it with SpEL, and evaluates it against the current method arguments.

Expression parsing utilities

private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

private boolean evaluateExpression(Method method, Object[] args, String condition) {
    if (StringUtil.isNotBlank(condition)) {
        Expression expression = EXPRESSION_PARSER.parseExpression(condition);
        StandardEvaluationContext context = getEvaluationContext(method, args);
        return expression.getValue(context, Boolean.class);
    }
    return false;
}

private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
    StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun());
    context.setBeanResolver(new BeanFactoryResolver(applicationContext));
    for (int i = 0; i < args.length; i++) {
        MethodParameter mp = ClassUtil.getMethodParameter(method, i);
        context.setVariable(mp.getParameterName(), args[i]);
    }
    return context;
}

The AuthFun class provides concrete permission‑checking methods that can be referenced from SpEL expressions.

AuthFun – Custom Permission Logic

public class AuthFun {
    /** Check if any role can access */
    public boolean permissionAll() { /* TODO */ }

    /** Check specific permission code */
    public boolean hasPermission(String permission) { /* TODO */ }

    /** Allow all requests */
    public boolean permitAll() { return true; }

    /** Only super‑admin role */
    public boolean denyAll() { return hasRole(RoleConstant.ADMIN); }

    /** User must be logged in */
    public boolean hasAuth() {
        if (Func.isEmpty(AuthUtil.getUser())) {
            // TODO: throw authentication exception
        }
        return true;
    }

    /** Time‑based access */
    public boolean hasTimeAuth(Integer start, Integer end) {
        int hour = DateUtil.hour();
        return hour >= start && hour <= end;
    }

    /** Single role check */
    public boolean hasRole(String role) { /* TODO */ }

    /** Multiple role check (any) */
    public boolean hasAnyRole(String... role) {
        BladeUser user = AuthUtil.getUser();
        if (user == null) return false;
        String[] roles = Func.toStrArray(user.getRoleName());
        for (String r : role) {
            if (CollectionUtil.contains(roles, r)) return true;
        }
        return false;
    }

    /** All roles must be present */
    public boolean hasAllRole(String... role) {
        for (String r : role) {
            if (!hasRole(r)) return false;
        }
        return true;
    }
}

Practical Usage

When applying the annotation, the value should contain a SpEL expression that calls one of the methods defined in AuthFun. Example:

@PreAuth("hasPermission('LM_QUERY,LM_QUERY_ALL')")
public T someApiMethod(...) { ... }

For multiple roles:

@PreAuth("hasAllRole('Admin','ChiefEngineer')")
public T anotherApiMethod(...) { ... }

Conclusion

By leveraging SpEL, permission configuration becomes highly extensible. Adding a new access scenario only requires implementing a corresponding method in AuthFun and using its name in the annotation expression, eliminating the need for repetitive annotation or aspect changes.

JavaSpELSpring BootCustom AnnotationaspectjAuthorization
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.