ATM There is no nice way of doing this as method "validPassword" on
UserStorageProvider is limited to return just boolean. No any additional
state can be returned from the credential validation ATM.
One slightly better way than thread-local could be to use the
KeycloakSession attributes. KeycloakSession is simply per-request
object, so you can use methods like getAttribute/setAttribute.
For some other use-case, we recently discuss to add
AuthenticationSessionModel as an additional thing to the
KeycloakContext. That would allow to retrieve current
AuthenticationSession inside the LDAPStorageProvider. However that is
not available yet and there may be some issues with the future
portability of this solution as LDAPStorageProvider (or any other
UserStorageProvider) shouldn't ideally have direct access to stuff like
AuthenticationSessionModel... So not convinced about this solution, just
mentioning for the completeness ;)
Marek
On 19/06/2019 18:12, Chris Smith wrote:
OK, I have an ugly hack. I hope someone might have a suggestion more
aligned with Keycloak architecture
My hack is to use a ThreadLocal to hold the serialized Kerberos ticket in the
LDAPStorageProvider
public static final String KERBEROS_NOT_SET = "*NOT_SET";
public static final ThreadLocal<String> SERILIZED_KERBEROS_TICKET = new
ThreadLocal<String>() {
@Override
protected String initialValue() {
return KERBEROS_NOT_SET;
}
};
public boolean validPassword(RealmModel realm, UserModel user, String password) {
if (kerberosConfig.isAllowKerberosAuthentication() &&
kerberosConfig.isUseKerberosForPasswordAuthentication()) {
// Use Kerberos JAAS (Krb5LoginModule)
KerberosUsernamePasswordAuthenticator authenticator =
factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
boolean isValid = authenticator.validUser(user.getUsername(), password);
if (isValid) {
SERILIZED_KERBEROS_TICKET.set(authenticator.getSerializedKerberosTicket());
}
return isValid;
} else {
// Use Naming LDAP API
...
And in the UsernamePasswordForm
1: override the validate password method
2: write the serialized ticket as a user session note to the
AuthenticationFlowContext.
@Override
public boolean validatePassword(AuthenticationFlowContext context, UserModel user,
MultivaluedMap<String, String> inputData) {
boolean isValid = super.validatePassword(context, user, inputData);
if (isValid) {
saveKerbTicketClaim(context);
}
return isValid;
}
private void saveKerbTicketClaim(AuthenticationFlowContext context) {
String kerbTicket = LDAPStorageProvider.SERILIZED_KERBEROS_TICKET.get();
if (!kerbTicket.equals(LDAPStorageProvider.KERBEROS_NOT_SET)) {
context.getAuthenticationSession()
.setUserSessionNote(KerberosConstants.GSS_DELEGATION_CREDENTIAL, kerbTicket);
}
LDAPStorageProvider.SERILIZED_KERBEROS_TICKET.remove();
return;
}
This is bad, but it works for now until someone can point out to me what I missed.
-----Original Message-----
From: keycloak-dev-bounces(a)lists.jboss.org <keycloak-dev-bounces(a)lists.jboss.org>
On Behalf Of Chris Smith
Sent: Monday, June 17, 2019 5:38 PM
To: keycloak-dev(a)lists.jboss.org; Marek Posolda <mposolda(a)redhat.com>
Subject: Re: [keycloak-dev] Putting a value into a custom Protocol mapper
correction
From: Chris Smith
Sent: Monday, June 17, 2019 5:15 PM
To: keycloak-dev(a)lists.jboss.org; Marek Posolda <mposolda(a)redhat.com>
Subject: RE: Putting a value into a custom Protocol mapper
I really need some help here
I can not find out how to add a Claim after a successful Kerberos User/Password login
This is as far as I know what is going on.
This is in the LDAPStorageProvider class. The KerberosUsernamePasswordAuthenticator has
been updated to hold the serialized Kerberos ticket. I now need to put this
somewhere/somehow so that my custom Protocol mapper will put it into the Access Token
public boolean validPassword(RealmModel realm, UserModel user, String password) {
if (kerberosConfig.isAllowKerberosAuthentication() &&
kerberosConfig.isUseKerberosForPasswordAuthentication()) {
// Use Kerberos JAAS (Krb5LoginModule)
Map<String, String> state = new HashMap<>();
KerberosUsernamePasswordAuthenticator authenticator =
factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
boolean isValidUser = authenticator.validUser(user.getUsername(),
password);
if (isValidUser) {
String delegationCredential =
authenticator.getSerializedKerberosTicket();
if (delegationCredential != null) {
state.put("odic-fms-sso-kerberos-ticket-mapper",
delegationCredential);
}
<< WHAT GOES HERE?? >>
return isValidUser;
This is from my servlet
KeycloakPrincipal<KeycloakSecurityContext> kcp =
(KeycloakPrincipal<KeycloakSecurityContext>)request.getUserPrincipal();
AccessToken at = kcp.getKeycloakSecurityContext().getToken();
Map<String, Object> otherClaims = at.getOtherClaims();
String otherClaim =
(String)otherClaims.get("odic-fms-sso-kerberos-ticket-mapper");
GSSCredential gssCredential =
KerberosSerializationUtils.deserializeCredential(otherClaim);
From: Chris Smith
Sent: Friday, June 14, 2019 4:24 PM
To: keycloak-dev@lists.jboss.org<mailto:keycloak-dev@lists.jboss.org>
Subject: Putting a value into a custom Protocol mapper
I'm trying to have a custom protocol mapper provide a serialized Kerberos ticket as a
claim
I have updated the KerberosUsernamePasswordAuthenticator so that it gets the ticket
public Subject authenticateSubject(String username, String password) throws
LoginException {
String principal = getKerberosPrincipal(username);
logger.debug("Validating password of principal: " + principal);
loginContext = new LoginContext("does-not-matter", null,
createJaasCallbackHandler(principal, password),
createJaasConfiguration());
loginContext.login();
serializedKerberosTicket = serializeTicket();
logger.debug("Principal " + principal + " authenticated
succesfully");
return loginContext.getSubject();
}
private String serializeTicket() {
KerberosTicket kerberosTicket = loginContext.getSubject()
.getPrivateCredentials(KerberosTicket.class)
.stream().findFirst().get();
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)){
oos.writeObject(kerberosTicket);
return Base64.getEncoder().encodeToString(bos.toByteArray());
} catch (IOException e) {
logger.error("Kerberos ticket serialization failed", e);
return null;
}
}
I reviewed the SPNEGOAuthenticator and traced it's execution to see how it adds the
Kerberos ticket and I do not see that as a workable approach as it is so different from
the Kerberos User/Password authenticator.
Where can my custom KerberosUsernamePasswordAuthenticator put the serialized ticket so
that my custom protocol mapper will get it and add it as a claim on my Access token?
I have looked and googled with no luck.
_______________________________________________
keycloak-dev mailing list
keycloak-dev(a)lists.jboss.org
https://lists.jboss.org/mailman/listinfo/keycloak-dev