Dateien nach "sourcecode/src/main/java/de/gmp/keycloak" hochladen

This commit is contained in:
Mirco Ropic 2025-07-25 14:16:58 +00:00
parent 706ac46d59
commit 8bf3b758d7
2 changed files with 229 additions and 0 deletions

View File

@ -0,0 +1,150 @@
package de.gmp.keycloak;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.apache.commons.net.util.SubnetUtils;
import java.util.Optional;
import java.util.stream.Stream;
import org.keycloak.authentication.AuthenticationFlowError;
import java.net.InetAddress;
import java.net.UnknownHostException;
// import org.keycloak.sessions.AuthenticationSessionModel;
// import org.keycloak.storage.UserStorageManager;
// import org.keycloak.credential.CredentialModel;
// import org.keycloak.storage.UserStorageManager;
// import org.keycloak.models.utils.KeycloakModelUtils;
// import org.keycloak.models.UserCredentialModel;
// import org.keycloak.credential.UserCredentialStore;
// import org.keycloak.authentication.AuthenticationProcessor;
// import org.keycloak.authentication.AuthenticationFlowError;
// import org.keycloak.authentication.CredentialValidator;
// import org.keycloak.credential.CredentialProvider;
// import org.keycloak.models.KeycloakSession;
// import org.keycloak.models.RealmModel;
// import org.keycloak.models.UserCredentialModel;
// import org.keycloak.models.UserModel;
// import org.keycloak.models.utils.FormMessage;
// import org.keycloak.sessions.AuthenticationSessionModel;
// import java.util.List;
public class IpAuthenticator implements Authenticator {
@Override
public void authenticate(AuthenticationFlowContext context) {
// SCHRITT 1: Prüfen, ob der Benutzer den IP-Login überspringen will.
String skip = context.getHttpRequest().getUri().getQueryParameters().getFirst("skip_ip_login");
if ("true".equals(skip)) {
context.attempted();
return;
}
// SCHRITT 2: IP-Prüfung wie gehabt
UserModel user = findUserByIp(context);
if (user != null) {
// Benutzer gefunden -> Zeige unsere spezielle Login-Seite an.
Response challenge = context.form()
.setAttribute("user", user)
.createForm("ip-login-page.ftl");
context.challenge(challenge);
} else {
// Kein passender Benutzer gefunden -> Fahre mit dem normalen Flow fort.
context.attempted();
}
}
@Override
public void action(AuthenticationFlowContext context) {
UserModel user = findUserByIp(context);
if (user != null) {
context.setUser(user);
context.success();
} else {
// Sicherheitshalber, falls die IP sich zwischenzeitlich ändert.
context.failure(AuthenticationFlowError.UNKNOWN_USER);
}
}
private UserModel findUserByIp(AuthenticationFlowContext context) {
RealmModel realm = context.getRealm();
KeycloakSession session = context.getSession();
String clientIp = context.getConnection().getRemoteAddr();
try (Stream<UserModel> userStream = session.users().searchForUserByUserAttributeStream(realm, "ipLogin", "1")) {
Optional<UserModel> matchingUser = userStream.filter(user -> {
Optional<String> ipChecker = user.getAttributeStream("ipAddresses").filter(ipBlock -> {
String[] ipBlocks = ipBlock.split(",");
for (String ipOrCidr : ipBlocks) {
//System.out.println(ipOrCidr);
if (isIpMatch(clientIp, ipOrCidr.trim())) {
//System.out.println(clientIp + " match!");
return true;
}
}
return false;
}).findFirst();
if (ipChecker.isPresent()) {
//System.out.println("Check: "+ipChecker);
return true;
} else {
return false;
}
}).findFirst();
if (matchingUser.isPresent()) {
// Speichere den gefundenen Benutzer für den nächsten Schritt (action)
UserModel user = matchingUser.get();
return user;
}
}
return null;
}
private boolean isIpMatch(String ip, String ipOrCidr) {
try {
if (ipOrCidr.contains("-")) { // Mehrere IPs nach Schreibweise 192.168.1.1-192.168.1.5
String[] parts = ipOrCidr.split("-");
String startIp = parts[0].trim();
String endIp = parts[1].trim();
try {
long ipToCheckLong = ipToLong(ip);
long startIpLong = ipToLong(startIp);
long endIpLong = ipToLong(endIp);
return ipToCheckLong >= startIpLong && ipToCheckLong <= endIpLong;
} catch (UnknownHostException e) {
e.printStackTrace();
return false;
}
} else { // Davon ausgehend, dass es sich nur um eine Adresse handelt
return ipOrCidr.equals(ip);
}
} catch (IllegalArgumentException e) {
return false;
}
}
private static long ipToLong(String ip) throws UnknownHostException {
InetAddress inetAddress = InetAddress.getByName(ip);
byte[] ipBytes = inetAddress.getAddress();
long result = 0;
for (byte b : ipBytes) {
result = (result << 8) | (b & 0xFF);
}
return result;
}
// Leere Methoden
@Override public boolean requiresUser() { return false; }
@Override public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { return true; }
@Override public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {}
@Override public void close() {}
}

View File

@ -0,0 +1,79 @@
package de.gmp.keycloak;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.List;
import java.util.Collections;
// import org.keycloak.models.UserModel;
// import org.keycloak.models.RealmModel;
public class IpAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "ip-authenticator";
private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
AuthenticationExecutionModel.Requirement.DISABLED
};
@Override
public String getDisplayType() {
return "Login per IP";
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public Authenticator create(KeycloakSession session) {
return new IpAuthenticator();
}
@Override
public void init(Config.Scope config) { }
@Override
public void postInit(KeycloakSessionFactory factory) { }
@Override
public void close() { }
@Override
public boolean isConfigurable() { return false; }
@Override
public boolean isUserSetupAllowed() { return false; }
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return new AuthenticationExecutionModel.Requirement[]{
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
AuthenticationExecutionModel.Requirement.DISABLED
};
}
@Override
public String getReferenceCategory() {
return null;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return Collections.emptyList();
}
@Override
public String getHelpText() {
return "Fügt eine 'Login mit 1 Klick'-Option hinzu, wenn die IP-Adresse bei den Attributen eines Benutzers gefunden wurde.";
}
}