wake-up-neo.net

Schlüsselumhang: Authentifizieren Sie den Benutzer mit einem benutzerdefinierten JWT

Situation: Wir verwenden Keycloak, um Benutzer in unserer Webanwendung (A) mithilfe des JavaScript-Adapters über einen normalen Browserauthentifizierungsfluss zu authentifizieren. Das funktioniert einwandfrei!

Ziel: Jetzt sollte eine neue Gruppe von Benutzern auf A zugreifen können. Sie melden sich jedoch mit Benutzername und Kennwort in einer vertrauenswürdigen Drittanbieteranwendung (B) ohne Keycloak an. In B haben sie einen Link zu A mit einer benutzerdefinierten JWT (die im Wesentlichen Benutzernamen und Rollen enthält) als Abfrageparameter. Wenn der Benutzer also auf den Link klickt, gelangt er zu unserem Anwendungseinstiegspunkt, an dem wir die JWT aus der URL lesen können. Was jetzt passieren muss, ist eine Art Token-Austausch. Wir möchten dieses benutzerdefinierte JWT an Keycloak senden, das analog zum normalen Anmeldevorgang ein Zugriffstoken zurücksendet.

Frage: Gibt es in Keycloak eine integrierte Unterstützung für einen solchen Anwendungsfall?

Versuche:

Ich habe versucht, einen vertraulichen Client mit "Signed JWT" als " Client Authenticator " zu erstellen, wie in den Dokumenten vorgeschlagen. Nach einigen Tests denke ich nicht, dass dies die richtige Strecke ist, auch wenn der Name vielversprechend ist.

Ein weiterer Track war " Vom Client vorgeschlagener Identitätsanbieter " durch Implementierung eines benutzerdefinierten Identitätsanbieters. Ich verstehe aber nicht, wie ich das JWT innerhalb der Anfrage senden kann.

Zur Zeit versuche ich, den Authentifizierungsablauf mit dem Befehl Autentication SPI um einen benutzerdefinierten Authentifikator zu erweitern.

Vielleicht ist es viel einfacher als ich denke. Kann mich jemand in die richtige Richtung führen?

7
Manuel

So konnte ich es endlich mit der in der Frage erwähnten Authentifizierung SPI lösen.

In Keycloak habe ich eine Kopie des "Browser" -Authentifizierungsflusses erstellt (da Sie die integrierten Flows nicht ändern können) und einen zusätzlichen Schritt "Portal JWT" eingeführt (siehe Abbildung unten). Ich habe es dann auf der Registerkarte "Bindings" an "Browser Flow" gebunden

 Custom Authentication Flow

Hinter "Portal JWT" verbirgt sich mein benutzerdefinierter Authentifikator, der die JWT aus dem Abfrageparameter in der Umleitungs-URI extrahiert und analysiert, um Benutzernamen und Rollen daraus zu erhalten. Der Benutzer wird dann mit dem benutzerdefinierten Attribut "isExternal" zu keycloak hinzugefügt. Hier ist ein Auszug davon:

public class JwtAuthenticator implements Authenticator {

private final JwtReader reader;

JwtAuthenticator(JwtReader reader) {
    this.reader = reader;
}

@Override
public void authenticate(AuthenticationFlowContext context) {
    Optional<String> externalCredential = hasExternalCredential(context);
    if (externalCredential.isPresent()) {
        ExternalUser externalUser = reader.read(context.getAuthenticatorConfig(), externalCredential.get());
        String username = externalUser.getUsername();
        UserModel user = context.getSession().users().getUserByUsername(username, context.getRealm());
        if (user == null) {
            user = context.getSession().users().addUser(context.getRealm(), username);
            user.setEnabled(true);
            user.setSingleAttribute("isExternal", "true");
        }
        for (String roleName : externalUser.getRoles()) {
            RoleModel role = context.getRealm().getRole(roleName);
            if (role == null) {
                role = context.getRealm().addRole(roleName);
            }
            user.grantRole(role);
        }
        context.setUser(user);
        context.success();
    } else {
        context.attempted();
    }
}

private Optional<String> hasExternalCredential(AuthenticationFlowContext context) {
    String redirectUri = context.getUriInfo().getQueryParameters().getFirst("redirect_uri);
    try {
        List<NameValuePair> queryParams = URLEncodedUtils.parse(new URI(redirectUri), "UTF-8");
        Optional<NameValuePair> jwtParam = queryParams.stream()
                .filter(nv -> "jwt".equalsIgnoreCase(nv.getName())).findAny();
        if (jwtParam.isPresent()) {
            String jwt = jwtParam.get().getValue();
            if (LOG.isDebugEnabled()) {
                LOG.debug("JWT found: " + jwt);
            }
            return Optional.of(jwt);
        }
    } catch (URISyntaxException e) {
        LOG.error("Redirect URL not as expected: " + redirectUri);
    }
    return Optional.empty();
}
2
Manuel