Mastering Dynamic Permission Checks in Spring Boot with Custom Annotations and SpEL

This article explains how to replace static permission annotations in Spring Boot with flexible SpEL expressions, showing step‑by‑step how to create a @PreAuth annotation, an AOP aspect, an expression parser, and a utility class that evaluates role‑ and time‑based access rules at runtime.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Mastering Dynamic Permission Checks in Spring Boot with Custom Annotations and SpEL

Why Simple Annotations Fall Short

Traditional permission checks using a custom annotation and an AOP aspect work for basic scenarios, but real‑world applications often require many different rules such as allowing any configured role, checking specific permissions, permitting all requests, restricting to super‑admin, requiring login, limiting access to a time window, or requiring multiple roles simultaneously.

Introducing SpEL for Flexible Rules

Spring Expression Language (SpEL) lets us embed executable expressions inside the annotation value, turning static checks into dynamic ones that can call arbitrary methods at runtime.

Define the Custom Annotation

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuth {
    /**
     * 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()            – only logged‑in users can access
     * hasTimeAuth(1,10)    – access allowed between 1‑10 o'clock
     * hasRole('管理员')    – user must have the specified role
     * hasAllRole('管理员','总工程师') – user must have all listed roles
     */
    String value();
}

Define the Aspect

The aspect intercepts methods or classes annotated with @PreAuth. It extracts the expression from the annotation, parses it with SpEL, and evaluates it against a custom context.

@Around("@annotation(PreAuthPath) || @within(PreAuthPath)")
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 permission logic
    return false;
}

Parse and Evaluate the SpEL Expression

private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

MethodSignature ms = point.getSignature() instanceof MethodSignature ? (MethodSignature) point.getSignature() : null;
Method method = ms.getMethod();
PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class);
String condition = preAuth.value();
if (StringUtil.isNotBlank(condition)) {
    Expression expression = EXPRESSION_PARSER.parseExpression(condition);
    Object[] args = point.getArgs();
    StandardEvaluationContext context = getEvaluationContext(method, args);
    return expression.getValue(context, Boolean.class);
}
return false;

Build the Evaluation Context

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 methodParam = ClassUtil.getMethodParameter(method, i);
        context.setVariable(methodParam.getParameterName(), args[i]);
    }
    return context;
}

Permission Utility (AuthFun)

The AuthFun class provides the methods referenced in SpEL expressions. Each method returns a boolean indicating whether the current request satisfies the rule.

public class AuthFun {
    public boolean permissionAll() { /* TODO */ }
    public boolean hasPermission(String permission) { /* TODO */ }
    public boolean permitAll() { return true; }
    public boolean denyAll() { return hasRole(RoleConstant.ADMIN); }
    public boolean hasAuth() {
        if (Func.isEmpty(AuthUtil.getUser())) {
            // TODO: throw exception or return false
        } else {
            return true;
        }
    }
    public boolean hasTimeAuth(Integer start, Integer end) {
        int hour = DateUtil.hour();
        return hour >= start && hour <= end;
    }
    public boolean hasRole(String role) { return hasAnyRole(role); }
    public boolean hasAllRole(String... role) {
        for (String r : role) {
            if (!hasRole(r)) return false;
        }
        return true;
    }
    public boolean hasAnyRole(String... role) {
        BladeUser user = AuthUtil.getUser();
        if (user == null) return false;
        String userRole = user.getRoleName();
        if (StringUtil.isBlank(userRole)) return false;
        String[] roles = Func.toStrArray(userRole);
        for (String r : role) {
            if (CollectionUtil.contains(roles, r)) return true;
        }
        return false;
    }
}

Practical Usage

Annotate a controller or service method with the desired SpEL expression. The expression must call a method defined in AuthFun and use single quotes for string literals.

@PreAuth("hasAllRole('管理员','总工程师')")
public T someApi(...) { ... }

Underlying Principle

When the aspect runs, it extracts the string inside @PreAuth, parses it with SpEL, and invokes the corresponding method on the AuthFun instance. This decouples permission logic from static annotations and makes it easy to add new rules by simply adding methods to AuthFun.

Conclusion

Using SpEL for permission checks turns rigid annotation‑based security into a highly extensible solution. New scenarios only require adding a method to AuthFun, after which the expression can be used directly in the annotation, providing a cleaner and more maintainable approach.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AOPSpELPermissionSpring BootCustom Annotation
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.