java 120 lines · 4 tabs

Method-Level Role Authorization with a Custom @RequireRole Annotation and Spring AOP

Shared by codesnips Jul 2026
4 tabs
package com.example.security;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {

    String[] value();

    boolean anyOf() default false;
}
4 files · java Explain with highlit

This snippet shows a small, self-contained role-based access control (RBAC) mechanism built with a custom annotation and a Spring AOP aspect, keeping authorization out of business logic. The pattern is useful when method-level checks are needed beyond URL-based security, or when the same guard must protect service methods invoked from multiple entry points (controllers, schedulers, messaging).

In RequireRole annotation, the marker is a plain @interface retained at RUNTIME and targeting METHOD. It carries a value() array of role names and an anyOf() flag that decides whether the caller must hold every listed role or just one. Because it is annotation-only, it stays a pure declaration of intent — no logic leaks into it.

RoleGuardAspect is where enforcement lives. The @Aspect is a Spring @Component so it is proxied and applied automatically. Its @Before("@annotation(requireRole)") pointcut binds the matched annotation instance directly into the advice parameter, so the aspect reads its value() and anyOf() without reflecting manually. The advice pulls the caller's granted authorities from an injected CurrentUserProvider, then delegates the decision to isAllowed. On failure it throws AccessDeniedException, which Spring maps to a 403 — the method body never runs.

The key trade-off is proxy-based interception: because Spring AOP wraps beans in proxies, the annotation is only honored on external calls into the bean, not on this.method() self-invocations. Guards therefore belong on public service methods reached through the proxy. Using @Before (rather than @Around) is deliberate — the check is a pre-condition with no need to alter the return value.

AccountService demonstrates realistic usage: closeAccount requires the single ADMIN role, while viewStatement accepts anyOf ADMIN or AUDITOR. The service reads cleanly because the cross-cutting concern is fully externalized. SecurityConfig completes the story by enabling @EnableAspectJAutoProxy and exposing a CurrentUserProvider bean that adapts the SecurityContextHolder. Pitfalls to watch: ensure roles are stored consistently (with or without a ROLE_ prefix) and that the aspect runs before transactional advice if it should block work early. This approach centralizes policy, makes intent declarative, and keeps tests focused on the aspect rather than every method.


Related snips

Share this code

Here's the card — post it anywhere.

Method-Level Role Authorization with a Custom @RequireRole Annotation and Spring AOP — share card
Link copied