HttpBasicAuthenticator.java
package org.keycloak.protocol.saml.profile.ecp.authenticator;
import org.keycloak.http.HttpRequest;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.common.util.Base64;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.util.List;
public class HttpBasicAuthenticator implements Authenticator {
private static final String BASIC = "Basic";
private static final String BASIC_PREFIX = BASIC + " ";
@Override
public void authenticate(final AuthenticationFlowContext context) {
final HttpRequest httpRequest = context.getHttpRequest();
final HttpHeaders httpHeaders = httpRequest.getHttpHeaders();
final String[] usernameAndPassword = getUsernameAndPassword(httpHeaders);
context.attempted();
if (usernameAndPassword != null) {
final RealmModel realm = context.getRealm();
final String username = usernameAndPassword[0];
final UserModel user = context.getSession().users().getUserByUsername(realm, username);
// to allow success/failure logging for brute force
context.getEvent().detail(Details.USERNAME, username);
context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, username);
if (user != null) {
final String password = usernameAndPassword[1];
final boolean valid = user.credentialManager().isValid(UserCredentialModel.password(password));
if (valid) {
if (isTemporarilyDisabledByBruteForce(context, user)) {
userDisabledAction(context, realm, user, Errors.USER_TEMPORARILY_DISABLED);
} else if (user.isEnabled()) {
userSuccessAction(context, user);
} else {
userDisabledAction(context, realm, user, Errors.USER_DISABLED);
}
} else {
notValidCredentialsAction(context, realm, user);
}
} else {
nullUserAction(context, realm, username);
}
}
}
protected void userSuccessAction(AuthenticationFlowContext context, UserModel user) {
context.getAuthenticationSession().setAuthenticatedUser(user);
context.success();
}
protected void userDisabledAction(AuthenticationFlowContext context, RealmModel realm, UserModel user, String eventError) {
context.getEvent().user(user);
context.getEvent().error(eventError);
context.failure(AuthenticationFlowError.INVALID_USER, Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, BASIC_PREFIX + "realm=\"" + realm.getName() + "\"")
.build());
}
protected void nullUserAction(final AuthenticationFlowContext context, final RealmModel realm, final String user) {
// no-op by default
}
protected void notValidCredentialsAction(final AuthenticationFlowContext context, final RealmModel realm, final UserModel user) {
context.getEvent().user(user);
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
context.failure(AuthenticationFlowError.INVALID_USER, Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, BASIC_PREFIX + "realm=\"" + realm.getName() + "\"")
.build());
}
private boolean isTemporarilyDisabledByBruteForce(AuthenticationFlowContext context, UserModel user) {
return (context.getRealm().isBruteForceProtected())
&& (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user));
}
private String[] getUsernameAndPassword(final HttpHeaders httpHeaders) {
final List<String> authHeaders = httpHeaders.getRequestHeader(HttpHeaders.AUTHORIZATION);
if (authHeaders == null || authHeaders.size() == 0) {
return null;
}
String credentials = null;
for (final String authHeader : authHeaders) {
if (authHeader.startsWith(BASIC_PREFIX)) {
final String[] split = authHeader.trim().split("\\s+");
if (split.length != 2) return null;
credentials = split[1];
}
}
try {
String val = new String(Base64.decode(credentials));
int seperatorIndex = val.indexOf(":");
if(seperatorIndex == -1) return new String[]{val};
String user = val.substring(0, seperatorIndex);
String pw = val.substring(seperatorIndex + 1);
return new String[]{user,pw};
} catch (final IOException e) {
throw new RuntimeException("Failed to parse credentials.", e);
}
}
@Override
public void action(final AuthenticationFlowContext context) {
}
@Override
public boolean requiresUser() {
return false;
}
@Override
public boolean configuredFor(final KeycloakSession session, final RealmModel realm, final UserModel user) {
return false;
}
@Override
public void setRequiredActions(final KeycloakSession session, final RealmModel realm, final UserModel user) {
}
@Override
public void close() {
}
}