Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save pimtel/104a0eef3e4ef18189757c043a4addaf to your computer and use it in GitHub Desktop.
Save pimtel/104a0eef3e4ef18189757c043a4addaf to your computer and use it in GitHub Desktop.
Keycloak RequiredAction for recording user information on login
# manual installation via jboss-cli
# idm login recording action
module add --name=de.tdlabs.idm.keycloak.idm-keycloak-ext-login-action \
--resources=/tmp/idm-keycloak-ext-login-action.jar \
--dependencies=org.keycloak.keycloak-common,org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.jboss.logging
{
"providers": [
"classpath:${jboss.home.dir}/providers/*",
"module:de.tdlabs.idm.keycloak.idm-keycloak-ext-login-action"
],
"admin": {
"realm": "master"
},
"eventsStore": {
"provider": "jpa",
"jpa": {
"exclude-events": [ "REFRESH_TOKEN" ]
}
},
"realm": {
"provider": "jpa"
},
"user": {
"provider": "jpa"
},
"userCache": {
"default" : {
"enabled": true
}
},
"userSessionPersister": {
"provider": "jpa"
},
"timer": {
"provider": "basic"
},
"theme": {
"staticMaxAge": 2592000,
"cacheTemplates": true,
"cacheThemes": true,
"folder": {
"dir": "${jboss.home.dir}/themes"
}
},
"scheduled": {
"interval": 900
},
"connectionsHttpClient": {
"default": {}
},
"connectionsJpa": {
"default": {
"dataSource": "java:jboss/datasources/KeycloakDS",
"databaseSchema": "update"
}
},
"realmCache": {
"provider": "default",
"default" : {
"enabled": true
}
},
"connectionsInfinispan": {
"provider": "default",
"default": {
"cacheContainer" : "java:comp/env/infinispan/Keycloak"
}
}
}
package de.tdlabs.idm.keycloak.ext.authentication;
import static java.time.LocalDateTime.now;
import static java.util.Arrays.asList;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.Config.Scope;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserModel;
public class LoginStatsRecordingRequiredActionProvider implements RequiredActionProvider, RequiredActionFactory {
private static final Logger LOG = Logger.getLogger(LoginStatsRecordingRequiredActionProvider.class);
private static final String PROVIDER_ID = "login_stats_action";
private static final String RECORD_LOGIN_STATISTICS_ACTION = "Record Login Statistics Action";
private static final String LOGIN_LOGIN_COUNT = "login.login-count";
private static final String LOGIN_FIRST_LOGIN_DATE = "login.first-login-date";
private static final String LOGIN_RECENT_LOGIN_DATE = "login.recent-login-date";
private static final String ONE = "1";
private static final LoginStatsRecordingRequiredActionProvider INSTANCE = new LoginStatsRecordingRequiredActionProvider();
@Override
public void close() {
// NOOP
}
@Override
public void evaluateTriggers(RequiredActionContext context) {
UserModel user = context.getUser();
try {
recordFirstLogin(user);
} catch (Exception ex) {
LOG.warnv(ex,"Couldn't record first login <{0}>", this);
}
try {
recordRecentLogin(user);
} catch (Exception ex) {
LOG.warnv(ex, "Couldn't record recent login <{0}>", this);
}
try {
recordLoginCount(user);
} catch (Exception ex) {
LOG.warnv(ex, "Couldn't record login count <{0}>", this);
}
}
private void recordLoginCount(UserModel user) {
List<String> list = user.getAttribute(LOGIN_LOGIN_COUNT);
if (list == null || list.isEmpty()) {
list = asList(ONE);
} else {
list = asList(String.valueOf(Long.parseLong(list.get(0)) + 1));
}
user.setAttribute(LOGIN_LOGIN_COUNT, list);
}
private void recordRecentLogin(UserModel user) {
user.setAttribute(LOGIN_RECENT_LOGIN_DATE, asList(now().toString()));
}
private void recordFirstLogin(UserModel user) {
List<String> list = user.getAttribute(LOGIN_FIRST_LOGIN_DATE);
if (list == null || list.isEmpty()) {
user.setAttribute(LOGIN_FIRST_LOGIN_DATE, asList(now().toString()));
}
}
@Override
public void requiredActionChallenge(RequiredActionContext context) {
// NOOP
}
@Override
public void processAction(RequiredActionContext context) {
context.success();
}
@Override
public RequiredActionProvider create(KeycloakSession session) {
return INSTANCE;
}
@Override
public void init(Scope config) {
LOG.infov("Creating IdM Keycloak extension <{0}>", this);
// NOOP
}
@Override
public void postInit(KeycloakSessionFactory factory) {
// NOOP
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getDisplayText() {
return RECORD_LOGIN_STATISTICS_ACTION;
}
}
<?xml version="1.0" ?>
<module xmlns="urn:jboss:module:1.1" name="de.eurodata.idm.keycloak.idm-keycloak-ext-login-action">
<!-- copy to $KEYCLOAK_HOME/modules/de/tdlabs/idm/keycloak/idm-keycloak-ext-login-action/main/ together with the jar -->
<!-- add module reference in provider section of keycloak-server.json under $KEYCLOAK_HOME/standalone/configuration -->
<resources>
<resource-root path="idm-keycloak-ext-login-action-1.0.0.BUILD-SNAPSHOT.jar"/>
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-server-spi"/>
<module name="org.jboss.logging"/>
</dependencies>
</module>
# place this under src/main/resources/META-INF/services
de.tdlabs.idm.keycloak.ext.authentication.LoginStatsRecordingRequiredActionProvider
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.tdlabs.idm</groupId>
<artifactId>idm-keycloak-ext-login-action</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<properties>
<keycloak.version>1.9.4.Final</keycloak.version>
<lombok.version>1.16.8</lombok.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment