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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
