contexa-identity

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)
Java
@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)
Java
@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"));
}

Injects a cookie value from the HTTP request within a security exception handler context.

@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
AttributeTypeDefaultDescription
value / nameString""Cookie name.
requiredbooleantrueWhether the cookie must be present.
defaultValueStringnoneDefault 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)
AttributeTypeDefaultDescription
requiredbooleantrueWhether the request body must be present.

@SecurityRequestAttribute

Injects a servlet request attribute value.

@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
AttributeTypeDefaultDescription
value / nameString""Request attribute name.
requiredbooleantrueWhether the attribute must be present.

@SecurityRequestHeader

Injects an HTTP request header value.

@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
AttributeTypeDefaultDescription
value / nameString""Header name.
requiredbooleantrueWhether the header must be present.
defaultValueStringnoneDefault value when header is absent and not required.

@SecuritySessionAttribute

Injects an HTTP session attribute value.

@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME)
AttributeTypeDefaultDescription
value / nameString""Session attribute name.
requiredbooleantrueWhether 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)
AttributeTypeDefaultDescription
valueClass<? extends Throwable>[]{}Exception types this handler catches. If empty, the handler method's exception parameter type is used.
priorityintLOWEST_PRECEDENCEHandler priority. Lower values have higher priority.
producesString[]{}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)
AttributeTypeDefaultDescription
value / basePackagesString[]{}Base packages to scan for applicability.
basePackageClassesClass<?>[]{}Classes whose packages are used as base packages.
assignableTypesClass<?>[]{}Specific types this advice applies to.
Java
@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);
    }
}