![JAR search and dependency download from the Maven repository](/logo.png)
io.hawt.system.Authenticator Maven / Gradle / Ivy
package io.hawt.system;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import jakarta.servlet.http.HttpServletRequest;
import io.hawt.util.Strings;
import io.hawt.web.auth.AuthenticationConfiguration;
import io.hawt.web.auth.AuthenticationThrottler;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Authenticator performs authentication using JAAS with the {@link LoginContext} for the chosen realm.
*
* Authenticator supports the following authentication methods:
*
* - a set of user name and password
* - oidc (bearer) access token
* - client certificates
*
*/
public class Authenticator {
private static final Logger LOG = LoggerFactory.getLogger(Authenticator.class);
public static final String HEADER_AUTHORIZATION = "Authorization";
public static final String X_J_HEADER_AUTHORIZATION = "X-Jolokia-Authorization";
public static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
public static final String AUTHENTICATION_SCHEME_BEARER = "Bearer";
public static final String ATTRIBUTE_X509_CERTIFICATE = "jakarta.servlet.request.X509Certificate";
private static Boolean websphereDetected;
private static Method websphereGetGroupsMethod;
private static Boolean jbosseapDetected;
private static Method jbosseapGetGroupsMethod;
private final AuthenticationConfiguration authConfiguration;
private String username;
private String password;
private X509Certificate[] certificates;
private Principal requestPrincipal;
/**
* Explicit username/password authenticator when authenticating users from login page.
*/
public Authenticator(AuthenticationConfiguration authConfiguration, String username, String password) {
this.authConfiguration = authConfiguration;
this.username = username;
this.password = password;
}
/**
* Request-based authenticator such as when authenticating direct Jolokia accesses.
*/
public Authenticator(HttpServletRequest request, AuthenticationConfiguration authConfiguration) {
this.authConfiguration = authConfiguration;
// Basic auth
extractAuthHeader(request, (username, password) -> {
this.username = username;
this.password = password;
});
// Client certificate auth
Object certificates = request.getAttribute(ATTRIBUTE_X509_CERTIFICATE);
if (certificates != null) {
this.certificates = (X509Certificate[]) certificates;
}
// existing auth - can be configured by Spring Security
this.requestPrincipal = request.getUserPrincipal();
}
/**
* Extracts username/password from Authorization header.
* Callback is invoked only when Authorization header is present.
*/
public static void extractAuthHeader(HttpServletRequest request, BiConsumer callback) {
extractAuthHeader(request, callback, false);
}
/**
* Extracts username/password from Authorization header.
* Callback is invoked only when Authorization header is present.
*/
public static void extractAuthHeader(HttpServletRequest request, BiConsumer callback,
boolean checkExtraHeaders) {
String authHeader = null;
if (checkExtraHeaders) {
authHeader = request.getHeader(X_J_HEADER_AUTHORIZATION);
}
if (Strings.isBlank(authHeader)) {
authHeader = request.getHeader(Authenticator.HEADER_AUTHORIZATION);
}
if (Strings.isBlank(authHeader)) {
return;
}
String[] parts = authHeader.trim().split(" ");
if (parts.length != 2) {
return;
}
String authType = parts[0];
String authInfo = parts[1];
if (authType.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) {
String decoded = new String(Base64.decodeBase64(authInfo));
int delimiter = decoded.indexOf(':');
if (delimiter < 0) {
return;
}
String username = decoded.substring(0, delimiter);
String password = decoded.substring(delimiter + 1);
callback.accept(username, password);
}
if (authType.equalsIgnoreCase(AUTHENTICATION_SCHEME_BEARER)) {
callback.accept("token", authInfo);
}
}
public boolean isUsernamePasswordSet() {
return Strings.isNotBlank(username) && Strings.isNotBlank(password);
}
public boolean hasNoCredentials() {
return (!isUsernamePasswordSet() || username.equals("public")) && certificates == null
&& requestPrincipal == null;
}
public static void logout(AuthenticationConfiguration authConfiguration, Subject subject) {
try {
LoginContext loginContext = new LoginContext(authConfiguration.getRealm(), subject);
loginContext.logout();
} catch (Exception e) {
LOG.warn("Error occurred while logging out", e);
}
}
public AuthenticateResult authenticate(Consumer callback) {
if (hasNoCredentials()) {
return AuthenticateResult.noCredentials();
}
// Try throttling authentication request when necessary
Optional throttler = authConfiguration.getThrottler();
AuthenticationThrottler.Attempt attempt = throttler
.map(t -> t.attempt(username))
.filter(AuthenticationThrottler.Attempt::isBlocked)
.orElse(null);
if (attempt != null) {
LOG.debug("Authentication throttled: {}", attempt);
return AuthenticateResult.throttled(attempt.retryAfter());
}
Subject subject = doAuthenticate();
if (subject == null) {
throttler.ifPresent(t -> t.increase(username));
return AuthenticateResult.notAuthorized();
}
throttler.ifPresent(t -> t.reset(username));
if (callback != null) {
try {
callback.accept(subject);
} catch (Exception e) {
LOG.warn("Failed to execute privileged action:", e);
}
}
return AuthenticateResult.authorized();
}
protected Subject doAuthenticate() {
String realm = authConfiguration.getRealm();
String role = authConfiguration.getRoles();
String rolePrincipalClasses = authConfiguration.getRolePrincipalClasses();
Configuration configuration = authConfiguration.getConfiguration();
try {
LOG.debug("doAuthenticate[realm={}, role={}, rolePrincipalClasses={}, configuration={}, username={}, password={}]",
realm, role, rolePrincipalClasses, configuration, username, "******");
Subject subject = new Subject();
login(subject, realm, configuration);
if (checkRoles(subject, role, rolePrincipalClasses)) {
return subject;
}
} catch (AccountException e) {
LOG.warn("Account failure", e);
} catch (LoginException e) {
LOG.warn("Login failed due to: {}", e.getMessage());
LOG.debug("Failed stacktrace:", e);
}
return null;
}
protected void login(Subject subject, String realm, Configuration configuration) throws LoginException {
CallbackHandler handler = createCallbackHandler();
// call the constructor with or without the configuration as it behaves differently
LoginContext loginContext;
if (configuration != null) {
loginContext = new LoginContext(realm, subject, handler, configuration);
} else {
loginContext = new LoginContext(realm, subject, handler);
}
loginContext.login();
}
private CallbackHandler createCallbackHandler() {
if (isUsernamePasswordSet()) {
return new UsernamePasswordCallbackHandler(username, password);
} else {
return new CertificateCallbackHandler(certificates);
}
}
protected boolean checkRoles(Subject subject, String role, String rolePrincipalClasses) {
if (Strings.isBlank(role)) {
LOG.debug("Skipping role check, no role configured");
return true;
}
if (role.equals("*")) {
LOG.debug("Skipping role check, all roles allowed");
return true;
}
boolean found;
if (isRunningOnWebsphere(subject)) {
found = checkIfSubjectHasRequiredRoleOnWebsphere(subject, role);
} else if (isRunningOnJbossEAP(subject)) {
found = checkIfSubjectHasRequiredRoleOnJbossEAP(subject, role);
} else {
if (Strings.isBlank(rolePrincipalClasses)) {
LOG.debug("Skipping role check, no rolePrincipalClasses configured");
return true;
}
found = checkIfSubjectHasRequiredRole(subject, role, rolePrincipalClasses);
}
if (!found) {
LOG.debug("User {} does not have the required role {}", username, role);
}
return found;
}
private boolean checkIfSubjectHasRequiredRole(Subject subject,
String role, String rolePrincipalClasses) {
String[] roleArray = role.split(",");
String[] rolePrincipalClazzes = rolePrincipalClasses.split(",");
boolean found = false;
for (String clazz : rolePrincipalClazzes) {
LOG.debug("Looking for rolePrincipalClass: {}", clazz);
for (Principal p : subject.getPrincipals()) {
LOG.debug("Checking principal, classname: {} toString: {}", p.getClass().getName(), p);
if (!p.getClass().getName().equals(clazz.trim())) {
LOG.debug("principal class {} doesn't match {}, continuing", p.getClass().getName(), clazz.trim());
continue;
}
for (String r : roleArray) {
if (r == null || !p.getName().equals(r.trim())) {
LOG.debug("role {} doesn't match {}, continuing", p.getName(), r);
continue;
}
LOG.debug("Matched role and role principal class");
found = true;
break;
}
if (found) {
break;
}
}
if (found) {
break;
}
}
return found;
}
private static boolean isRunningOnWebsphere(Subject subject) {
if (websphereDetected == null) {
boolean onWebsphere = false;
for (Principal p : subject.getPrincipals()) {
LOG.trace("Checking principal for IBM specific interfaces: {}", p);
onWebsphere = implementsInterface(p, "com.ibm.websphere.security.auth.WSPrincipal");
}
LOG.trace("Checking if we are running using a IBM Websphere specific LoginModule: {}", onWebsphere);
websphereDetected = onWebsphere;
}
return websphereDetected;
}
private static boolean isRunningOnJbossEAP(Subject subject) {
if (jbosseapDetected == null) {
boolean onJbossEAP = false;
for (Principal p : subject.getPrincipals()) {
LOG.trace("Checking principal for JBoss EAP specific interfaces: {} {}", p, p.getClass().getName());
onJbossEAP = "org.jboss.security.SimplePrincipal".equals(p.getClass().getName());
if (onJbossEAP) break;
}
LOG.trace("Checking if we are running using a Jboss EAP specific LoginModule: {}", onJbossEAP);
jbosseapDetected = onJbossEAP;
}
return jbosseapDetected;
}
private static boolean checkIfSubjectHasRequiredRoleOnWebsphere(Subject subject, String role) {
LOG.debug("Running on websphere: checking if the Role {} is in the set of groups in WSCredential", role);
for (final Object cred : subject.getPublicCredentials()) {
LOG.debug("Checking credential {} if it is a WebSphere specific WSCredential containing group info", cred);
if (implementsInterface(cred, "com.ibm.websphere.security.cred.WSCredential")) {
try {
Method groupsMethod = getWebSphereGetGroupsMethod(cred);
@SuppressWarnings("unchecked") final List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy