150 lines
5.7 KiB
Java
150 lines
5.7 KiB
Java
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() {}
|
|
} |