SecurityExceptionHandler.java
package com.project.auth.config.auth.security;
import com.project.auth.application.support.audit.AuthAuditEventType;
import com.project.auth.application.support.logging.LogSanitizer;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import java.util.Objects;
public class SecurityExceptionHandler implements AuthenticationEntryPoint, AccessDeniedHandler {
private static final Logger log = LoggerFactory.getLogger(SecurityExceptionHandler.class);
private final HandlerExceptionResolver handlerExceptionResolver;
private final SecurityAuditTrailWriter securityAuditTrailWriter;
public SecurityExceptionHandler(
HandlerExceptionResolver handlerExceptionResolver,
SecurityAuditTrailWriter securityAuditTrailWriter
) {
this.handlerExceptionResolver = Objects.requireNonNull(
handlerExceptionResolver,
"handlerExceptionResolver must not be null"
);
this.securityAuditTrailWriter = Objects.requireNonNull(
securityAuditTrailWriter,
"securityAuditTrailWriter must not be null"
);
}
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException
) {
recordAudit(request, AuthAuditEventType.AUTHENTICATION_REQUIRED, "Authentication required.");
if (response.isCommitted()) {
log.warn("Response already committed; cannot render authentication error body. requestPath={}",
LogSanitizer.requestPath(request.getRequestURI()));
return;
}
handlerExceptionResolver.resolveException(request, response, null, authException);
}
@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException
) {
recordAudit(request, AuthAuditEventType.ACCESS_DENIED, "Access denied.");
if (response.isCommitted()) {
log.warn("Response already committed; cannot render access-denied error body. requestPath={}",
LogSanitizer.requestPath(request.getRequestURI()));
return;
}
handlerExceptionResolver.resolveException(request, response, null, accessDeniedException);
}
private void recordAudit(HttpServletRequest request, AuthAuditEventType type, String description) {
try {
securityAuditTrailWriter.record(request, type, description);
} catch (RuntimeException auditFailure) {
log.warn("Security audit write failed; continuing with response rendering. type={}",
type, auditFailure);
}
}
}