ASEP Annotations
An annotation system for the ASEP security exception-handling path. It injects the current user, authentication object, and request metadata into @SecurityExceptionHandler method parameters and handles security failures at the filter-chain level before normal Spring MVC exception handling.
Overview
ASEP annotations are resolved as part of the Contexa security pipeline and provide a dedicated exception-handling path for security failures before normal Spring MVC exception handling. The runtime builds an ASEPFilter, a SecurityExceptionHandlerInvoker, and annotation resolvers from the current authentication flow's ASEP attributes.
ASEP Processing Flow
=====================
[Authentication Flow Config]
|
v
[AsepConfigurer]
|
+--- builds ---> [SecurityExceptionHandlerInvoker]
|
+--- builds ---> [ASEPFilter]
|
v
[Security Exception Path] ---> [@SecurityExceptionHandler]
| |
v v
[Argument Resolvers] [Custom Error Response]
[Return Value Handlers]
Enabling ASEP in DSL
Call asep() for each authentication method when you need to register custom ASEP exception argument resolvers or return value handlers.
registry
.form(form -> form
.loginPage("/login")
.asep(asep -> asep
.exceptionArgumentResolver(customResolver)))
.session(Customizer.withDefaults())
.build();
ASEP attribute classes: FormAsepAttributes, RestAsepAttributes, OttAsepAttributes, PasskeyAsepAttributes, MfaAsepAttributes, MfaOttAsepAttributes, MfaPasskeyAsepAttributes
Argument Injection Annotations
These annotations enable direct injection of security context data into ASEP handler method parameters. Each has a corresponding SecurityHandlerMethodArgumentResolver implementation and is resolved inside the ASEP runtime, not as a general-purpose Spring MVC controller argument feature.
@SecurityPrincipal
Injects the current security principal into a method parameter. Resolves from SecurityContextHolder.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
@SecurityExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> handleAuthError(
@SecurityPrincipal Object principal,
AuthenticationException ex) {
// principal is resolved from SecurityContextHolder
return ResponseEntity.status(401).body(
new ErrorResponse("AUTH_FAILED", ex.getMessage()));
}
@AuthenticationObject
Injects the full Authentication object from the security context, providing access to credentials, authorities, and authentication details.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
@SecurityExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDenied(
@AuthenticationObject Authentication auth,
AccessDeniedException ex) {
String username = auth != null ? auth.getName() : "anonymous";
return ResponseEntity.status(403).body(
new ErrorResponse("ACCESS_DENIED", "User " + username + " lacks permission"));
}
@SecurityCookieValue
Injects a cookie value from the HTTP request within a security exception handler context.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
| Attribute | Type | Default | Description |
|---|---|---|---|
value / name | String | "" | Cookie name. |
required | boolean | true | Whether the cookie must be present. |
defaultValue | String | none | Default value when cookie is absent and not required. |
@SecurityRequestBody
Deserializes the HTTP request body within a security exception handler context using the configured HttpMessageConverter instances.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
| Attribute | Type | Default | Description |
|---|---|---|---|
required | boolean | true | Whether the request body must be present. |
@SecurityRequestAttribute
Injects a servlet request attribute value.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
| Attribute | Type | Default | Description |
|---|---|---|---|
value / name | String | "" | Request attribute name. |
required | boolean | true | Whether the attribute must be present. |
@SecurityRequestHeader
Injects an HTTP request header value.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
| Attribute | Type | Default | Description |
|---|---|---|---|
value / name | String | "" | Header name. |
required | boolean | true | Whether the header must be present. |
defaultValue | String | none | Default value when header is absent and not required. |
@SecuritySessionAttribute
Injects an HTTP session attribute value.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
| Attribute | Type | Default | Description |
|---|---|---|---|
value / name | String | "" | Session attribute name. |
required | boolean | true | Whether the attribute must be present. |
Exception Handling Annotations
@SecurityExceptionHandler
Marks a method as a security exception handler. Methods annotated with this are invoked by the SecurityExceptionHandlerInvoker when a matching exception occurs within the security filter chain. This is the security-layer equivalent of Spring MVC's @ExceptionHandler.
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)
| Attribute | Type | Default | Description |
|---|---|---|---|
value | Class<? extends Throwable>[] | {} | Exception types this handler catches. If empty, the handler method's exception parameter type is used. |
priority | int | LOWEST_PRECEDENCE | Handler priority. Lower values have higher priority. |
produces | String[] | {} | Producible media types (e.g., "application/json"). |
@SecurityControllerAdvice
Marks a class as a security controller advice bean, analogous to Spring MVC's @ControllerAdvice. Classes annotated with this are scanned by the SecurityExceptionHandlerMethodRegistry to discover @SecurityExceptionHandler methods.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME)
| Attribute | Type | Default | Description |
|---|---|---|---|
value / basePackages | String[] | {} | Base packages to scan for applicability. |
basePackageClasses | Class<?>[] | {} | Classes whose packages are used as base packages. |
assignableTypes | Class<?>[] | {} | Specific types this advice applies to. |
@SecurityControllerAdvice
public class GlobalSecurityExceptionAdvice {
@SecurityExceptionHandler(AuthenticationException.class)
@SecurityResponseBody
public ResponseEntity<Map<String, Object>> handleAuthFailure(
@AuthenticationObject Authentication auth,
@SecurityRequestHeader(value = "X-Request-ID", required = false) String requestId,
AuthenticationException ex) {
Map<String, Object> body = Map.of(
"error", "AUTHENTICATION_FAILED",
"message", ex.getMessage(),
"requestId", requestId != null ? requestId : "unknown"
);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body);
}
@SecurityExceptionHandler(AccessDeniedException.class)
@SecurityResponseBody
public ResponseEntity<Map<String, Object>> handleAccessDenied(
@SecurityPrincipal Object principal,
AccessDeniedException ex) {
Map<String, Object> body = Map.of(
"error", "ACCESS_DENIED",
"message", ex.getMessage()
);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(body);
}
}
Response Annotations
@SecurityResponseBody
Indicates that the return value of a @SecurityExceptionHandler method should be serialized to the HTTP response body using the configured HttpMessageConverter instances. Internally meta-annotated with Spring's @ResponseBody.
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME)
Complete Example
A complete example of security exception handling using ASEP annotations and @SecurityControllerAdvice.
@SecurityControllerAdvice
public class GlobalSecurityExceptionHandler {
@SecurityExceptionHandler(AuthenticationException.class)
@SecurityResponseBody
public ErrorResponse handleAuthError(
AuthenticationException ex,
@AuthenticationObject Authentication auth,
@SecurityRequestHeader("X-Device-Id") String deviceId) {
String principalName = auth != null ? auth.getName() : "anonymous";
return new ErrorResponse("AUTH_FAILED", principalName + " on device " + deviceId);
}
@SecurityExceptionHandler(AccessDeniedException.class)
@SecurityResponseBody
public ErrorResponse handleAccessDenied(
AccessDeniedException ex,
@SecurityPrincipal Object principal,
@SecurityCookieValue("ctxa_trace") String traceId) {
return new ErrorResponse("ACCESS_DENIED", "principal=" + principal + ", trace=" + traceId);
}
}