Use Cases
1. Perform Authorization Check with the help of HTTP Request Headers
We want to perform an authorization check without touching the controller logic.
Context
In a typical Spring Boot application, authorization logic is often placed in the controller or service methods. However, this tightly couples business logic with security concerns and violates separation of concerns.
We want to perform authorization checks (e.g., checking if the user has permission to access a resource) without modifying the controller or service layer logic.
Solution
We will:
Define a custom annotation
Apply it to the method we want to protect
Create an Aspect that intercepts the method and performs authorization
Keep the business logic clean and focused
Create the Custom Annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireAuthorization {
String role() default "ADMIN";
}
This annotation will mark methods that require a role-based check.
The default required role is "ADMIN"
but it's customizable.
Create the Aspect
@Aspect
@Component
public class AuthorizationAspect {
private final HttpServletRequest request;
public AuthorizationAspect(HttpServletRequest request) {
this.request = request;
}
@Before("@annotation(requireAuthorization)")
public void checkAuthorization(RequireAuthorization requireAuthorization) {
// Extract the expected role from annotation
String requiredRole = requireAuthorization.role();
// Extract actual role from HTTP header
String userRole = request.getHeader("X-USER-ROLE");
// Null or missing role
if (userRole == null) {
throw new UnauthorizedException("Missing role in request header");
}
// Role mismatch
if (!userRole.equalsIgnoreCase(requiredRole)) {
throw new UnauthorizedException("Access denied. Required role: " + requiredRole);
}
// Authorized – proceed
}
}
Notes:
The aspect uses
@Before
to intercept method execution.HttpServletRequest
is injected and used to read headers or parameters.We can extend this to check cookies, JWT claims, or session attributes.
Define Custom Exception
@ResponseStatus(HttpStatus.FORBIDDEN)
public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
}
Apply the Annotation on a Controller or Service Method
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@RequireAuthorization(role = "ADMIN")
@DeleteMapping("/delete/{id}")
public ResponseEntity<Void> deleteResource(@PathVariable String id) {
// Controller logic untouched
return ResponseEntity.ok().build();
}
}
2. Trigger an Notification Event on Successful Payment Response
We want to trigger an event (e.g., sending a notification) only after a controller method returns successfully, without cluttering the controller logic. Request and Response object should be available while triggering that event to capture some of the details from it.
Approach
Use a custom annotation to mark methods where events should be triggered.
Write an aspect using
@AfterReturning
to intercept only successful executions.Inject the
HttpServletRequest
and access the returned object (response).Capture necessary data and publish a custom Spring event.
Solution
Custom Annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TriggerNotificationOnSuccess {
String eventType() default "PAYMENT_SUCCESS";
}
Event Class
public class PaymentSuccessEvent extends ApplicationEvent {
private final HttpServletRequest request;
private final Object response;
private final String eventType;
public PaymentSuccessEvent(Object source, HttpServletRequest request, Object response, String eventType) {
super(source);
this.request = request;
this.response = response;
this.eventType = eventType;
}
public HttpServletRequest getRequest() {
return request;
}
public Object getResponse() {
return response;
}
public String getEventType() {
return eventType;
}
}
Aspect to Intercept and Publish Event
@Aspect
@Component
public class NotificationTriggerAspect {
private final ApplicationEventPublisher eventPublisher;
private final HttpServletRequest request;
public NotificationTriggerAspect(ApplicationEventPublisher eventPublisher, HttpServletRequest request) {
this.eventPublisher = eventPublisher;
this.request = request;
}
@AfterReturning(pointcut = "@annotation(annotation)", returning = "result")
public void afterSuccessfulResponse(TriggerNotificationOnSuccess annotation, Object result) {
String eventType = annotation.eventType();
PaymentSuccessEvent event = new PaymentSuccessEvent(
this,
request,
result,
eventType
);
eventPublisher.publishEvent(event);
}
}
Sample Controller
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
@PostMapping("/pay")
@TriggerNotificationOnSuccess(eventType = "PAYMENT_SUCCESS")
public PaymentResponse pay(@RequestBody PaymentRequest request) {
// Payment processing logic
return new PaymentResponse("TXN123456", "SUCCESS");
}
}
Event Listener
@Component
public class PaymentSuccessEventListener {
@EventListener
public void onPaymentSuccess(PaymentSuccessEvent event) {
HttpServletRequest request = event.getRequest();
Object response = event.getResponse();
// Extract relevant info from request and response
String userAgent = request.getHeader("User-Agent");
String transactionId = ((PaymentResponse) response).getTransactionId();
// Trigger notification logic
System.out.println("Sending notification for transaction: " + transactionId + " from " + userAgent);
}
}
Last updated
Was this helpful?