AuthenticationEventsLogger.java
/*
* Copyright (C) 2024 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.security;
import io.micrometer.core.instrument.Metrics;
import java.lang.invoke.MethodHandles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;
import org.tailormap.api.security.events.DefaultAuthenticationFailureEvent;
@Component
public class AuthenticationEventsLogger {
private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static String getIPAddressInfo(AbstractAuthenticationEvent event) {
String extraInfo = "";
// prevent leaking personal data in logs unless trace logging is enabled
if (logger.isTraceEnabled()
&& event.getAuthentication().getDetails() instanceof WebAuthenticationDetails details) {
extraInfo = " (IP: %s)".formatted(details.getRemoteAddress());
}
return extraInfo;
}
@EventListener
public void onSuccess(AuthenticationSuccessEvent success) {
String authInfo = "";
String clientId = "";
String clientName = "";
if (success.getSource() instanceof OAuth2LoginAuthenticationToken token) {
// prevent leaking personal data in logs unless trace logging is enabled
String userClaims = "";
if (logger.isTraceEnabled() && token.getPrincipal() instanceof DefaultOidcUser oidcUser) {
userClaims = ", user claims: " + oidcUser.getUserInfo().getClaims();
}
clientId = token.getClientRegistration().getClientId();
clientName = token.getClientRegistration().getClientName();
authInfo = "via OIDC registration \"%s\" with client ID %s%s".formatted(clientName, clientId, userClaims);
}
if (success.getSource() instanceof UsernamePasswordAuthenticationToken) {
authInfo = "using username/password";
}
logger.info(
"Authentication successful for user \"{}\"{}, granted authorities: {}, {}",
// prevent leaking personal data in logs unless trace logging is enabled
logger.isTraceEnabled() ? success.getAuthentication().getName() : "<username hidden>",
getIPAddressInfo(success),
success.getAuthentication().getAuthorities().toString(),
authInfo);
Metrics.counter(
"tailormap_authentication_success",
"type",
success.getSource() instanceof OAuth2LoginAuthenticationToken ? "oauth2" : "username_password",
"clientId",
clientId,
"clientName",
clientName)
.increment();
}
@EventListener
public void onFailure(AbstractAuthenticationFailureEvent failure) {
String userInfo = "";
if (failure.getAuthentication().getPrincipal() != null) {
userInfo = " for user \"%s\"".formatted(failure.getAuthentication().getPrincipal());
}
logger.info(
"Authentication failure: {} {}{}",
failure.getException().getMessage(),
// in this case logging the "login" is useful/warranted for analysis
userInfo,
getIPAddressInfo(failure));
Metrics.counter(
"tailormap_authentication_failure",
"type",
failure.getSource() instanceof OAuth2LoginAuthenticationToken ? "oauth2" : "username_password")
.increment();
}
@EventListener
public void onDefaultAuthenticationFailureEvent(DefaultAuthenticationFailureEvent event) {
logger.info("Default authentication failure", event.getException());
}
}