Keycloak User Storage SPI Implementation

by amj   Last Updated August 14, 2019 05:26 AM - source

I'm trying to implement a custom keycloack Authenticator SPI for authenticating against an external Datasource. Spring boot Rest Service is also available, I can also use that.

Use case I am trying to solve is

User is presented keycloak login screen. Onsubmission User is validated against external Datasource.

Retrieve some attributes from external datasource, map it to keycloak's id and access token.

Also put in a condition of user restriction of same user logging in multiple times at the same time.

I was thinking, it could be solved by retrieving user session information that's available in the keycloak datasource. If i use external datasource, does keycloak still maintain session information?

I followed section 8.3 of the official guide (https://www.keycloak.org/docs/latest/server_development/index.html#_auth_spi_walkthrough), which is very similar to what I need.

Now i skipped and started as per section 11(https://www.keycloak.org/docs/latest/server_development/index.html#_user-storage-spi) seems more apt as well.

What I have done is started with implementing custom authenticator SPI thought it isn't the right way and now implemented UserStorageProvider.

/***
 * From UserLookupProvider
 */
public UserModel getUserById(String id, RealmModel realm) {
    System.out.println("ID: " + id + ":REALM:" + realm);
    StorageId storageId = new StorageId(id);
    /**
     * StorageId.getExternalId() method is invoked to obtain 
     * the username embeded in the id parameter
     */
    String username = storageId.getExternalId();
    System.out.println("Name:" + username);
    return getUserByUsername(username, realm);
}

/***
 * From UserLookupProvider
 * This method is invoked by the Keycloak login page when a user logs in
 */
public UserModel getUserByUsername(String username, RealmModel realm) {
    System.out.println("USERNAME: " + username + ":REALM:" + realm);
    UserModel userModel = loadedUsers.get(username);
    if (userModel == null) {
        String password = properties.getProperty(username);
        if (password != null) {
            userModel = createUserModel(realm, username);
            System.out.println("New UserModel:");
            System.out.println(userModel.toString());
            loadedUsers.put(username, userModel);
        }
    }
    return userModel;
}

protected UserModel createUserModel(RealmModel realm, String username) {
    return new AbstractUserAdapter(session, realm, model) {
        @Override
        public String getUsername() {
            return username;
        }
    };
}

Followed the doc(https://www.keycloak.org/docs/latest/server_development/index.html#packaging-and-deployment-2)

The class files for our provider implementation should be placed in a jar. You also have to declare the provider factory class within the META-INF/services/org.keycloak.storage.UserStorageProviderFactory file.

Question here is: jar I created doesn't have services directory inside "META-INF" folder, do I need to create manually and add it?

org.keycloak.examples.federation.properties.FilePropertiesStorageFactory Once you create the jar you can deploy it using regular WildFly means: copy the jar into the deploy/ directory or using the JBoss CLI.

After creating jar using maven, copied jar to "keycloak-6.0.1\standalone\deployments" folder. but i don't see my provider in the "User Federation list"User Federation List

Any suggestion/help would be greatly appreciated!!

Thanks in advance for your suggestions.



Answers 2


Incase if anybody ran into issues like this:

UserStorage SPI wasn't displaying because of META-INF/services folder. It's provided in the documentation but it isn't clear

In src/main/resources, create a folder structure META-INF/services

Create a file called org.keycloak.storage.UserStorageProviderFactory (the whole thing is the filename) in META-INF/services directory. Its contents is the fully qualified class name of your SPI: com.test.UserSpi

deploy jar

amj
amj
August 14, 2019 03:13 AM

I'm not exactly sure what do you need. Lets start by differentiating Authentication SPI (federated identity check) vs User Provider SPI (federated users). The first one (section 8 of the doc - is more about focusing on authenticating users against an external service - similar to facebook, or google). Federated user store is more like you have your awn users in a legacy system with their legacy "roles structure", and you basically want to manage them vie keycloak (either by importing them, or by querying the via some API - this would be section 11 of that documentation). So please decide what is indeed what you need.

2nd, you mention following:

>  User is presented keycloak login screen. Onsubmission User is
> validated against external Datasource.
> 
> Retrieve some attributes from external datasource, map it to
> keycloak's id and access token.
> 
> Also put in a condition of user restriction of same user logging in
> multiple times at the same time.
> 
> I was thinking, it could be solved by retrieving user session
> information that's available in the keycloak datasource. If i use
> external datasource, does keycloak still maintain session information?

What do you mean by: Retrieve some attributes from external datasource, map it to keycloak's id and access token. ? Usually you only retrieve user core information, plus maybe roles and other custom attributes (not session information). Keycloak itself as an Authorization Server based on openIDConnect, will generate acces tokens which already contains information about what protected resource can by accessed by whom, so you dont really need to import any session from somewhere else, or concern yourself with the generation of said tokens.

regarding: Also put in a condition of user restriction of same user logging in multiple times at the same time. what exactly are you trying to accomplish (or avoid?) when you log the 1st time your client receives a Bearer token valid for X amount of time, within that time you wont need to log yourself again, until the token expires or is roveked; again something your Auth server takes care of, not something you implement. Is there something more specific you want?

I was thinking, it could be solved by retrieving user session information that's available in the keycloak datasource. If i use external datasource, does keycloak still maintain session information? This doesn't sound right, what session data do you refer to? or you need access to? your userdata, scopes, roles, etc can be accessed via KEycloak Rest API (https://www.keycloak.org/docs-api/6.0/rest-api/index.html#_overview). Your external datasource is for user related core data (not external sessions), why do you think you need to import an external session?

tony _008
tony _008
August 14, 2019 05:24 AM

Related Questions




Realm creation in keycloak

Updated April 11, 2018 06:26 AM

Keycloak Silent Authentication

Updated March 21, 2019 21:26 PM