[keycloak-dev] Putting a value into a custom Protocol mapper

Marek Posolda mposolda at redhat.com
Mon Jun 24 09:00:04 EDT 2019


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 at lists.jboss.org <keycloak-dev-bounces at lists.jboss.org> On Behalf Of Chris Smith
> Sent: Monday, June 17, 2019 5:38 PM
> To: keycloak-dev at lists.jboss.org; Marek Posolda <mposolda at 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 at lists.jboss.org; Marek Posolda <mposolda at 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 at lists.jboss.org<mailto:keycloak-dev at 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 at lists.jboss.org
> https://lists.jboss.org/mailman/listinfo/keycloak-dev




More information about the keycloak-dev mailing list