From e1410d1e45e62b1eba80881a53cd435b2cdc4410 Mon Sep 17 00:00:00 2001 From: Hylke van der Schaaf Date: Thu, 22 Feb 2024 15:41:25 +0100 Subject: [PATCH] Added cache to basic authentication module --- CHANGELOG.md | 1 + .../auth/basic/BasicAuthProvider.java | 3 ++ .../auth/basic/DatabaseHandler.java | 42 ++++++++++++++++++- .../ilt/frostserver/util/user/UserData.java | 27 ++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69e40e4d5..e84c25365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ make sure to check and update your HELM settings. * Fixed security queries running as normal user, resulting in too narrow access. * Improved the memory efficiency of the DataArray resultFormat. * Added checks for maximum username and password lengths. +* Added cache to basic authentication module to avoid calling crypt for users that are previously authenticated. ## Release version 2.2.0 diff --git a/FROST-Server.Auth.Basic/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/basic/BasicAuthProvider.java b/FROST-Server.Auth.Basic/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/basic/BasicAuthProvider.java index c87e9f84f..8f71d7c59 100644 --- a/FROST-Server.Auth.Basic/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/basic/BasicAuthProvider.java +++ b/FROST-Server.Auth.Basic/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/basic/BasicAuthProvider.java @@ -61,6 +61,9 @@ public class BasicAuthProvider implements AuthProvider, LiquibaseUser, ConfigDef @DefaultValueInt(MAX_USERNAME_LENGTH) public static final String TAG_MAX_USERNAME_LENGTH = "maxUsernameLength"; + @DefaultValueInt(60_000) + public static final String TAG_USER_CACHE_LIFE_MS = "userCacheLifeMs"; + @DefaultValue("FROST-Server") public static final String TAG_AUTH_REALM_NAME = "realmName"; diff --git a/FROST-Server.Auth.Basic/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/basic/DatabaseHandler.java b/FROST-Server.Auth.Basic/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/basic/DatabaseHandler.java index 42a08637b..543670ade 100644 --- a/FROST-Server.Auth.Basic/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/basic/DatabaseHandler.java +++ b/FROST-Server.Auth.Basic/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/basic/DatabaseHandler.java @@ -20,6 +20,7 @@ import static de.fraunhofer.iosb.ilt.frostserver.auth.basic.BasicAuthProvider.LIQUIBASE_CHANGELOG_FILENAME; import static de.fraunhofer.iosb.ilt.frostserver.auth.basic.BasicAuthProvider.TAG_AUTO_UPDATE_DATABASE; import static de.fraunhofer.iosb.ilt.frostserver.auth.basic.BasicAuthProvider.TAG_PLAIN_TEXT_PASSWORD; +import static de.fraunhofer.iosb.ilt.frostserver.auth.basic.BasicAuthProvider.TAG_USER_CACHE_LIFE_MS; import static de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.ConnectionUtils.TAG_DB_URL; import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.ConnectionUtils; @@ -38,6 +39,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.apache.commons.collections4.map.PassiveExpiringMap; import org.jooq.Condition; import org.jooq.DSLContext; import org.jooq.Record1; @@ -66,6 +68,8 @@ public class DatabaseHandler { private final String connectionUrl; private boolean maybeUpdateDatabase; + private PassiveExpiringMap cache; + public static void init(CoreSettings coreSettings) { if (INSTANCES.get(coreSettings) == null) { createInstance(coreSettings); @@ -93,6 +97,11 @@ private DatabaseHandler(CoreSettings coreSettings) { maybeUpdateDatabase = authSettings.getBoolean(TAG_AUTO_UPDATE_DATABASE, BasicAuthProvider.class); plainTextPassword = authSettings.getBoolean(TAG_PLAIN_TEXT_PASSWORD, BasicAuthProvider.class); connectionUrl = authSettings.get(TAG_DB_URL, ConnectionUtils.class, false); + int userCacheLifeMs = authSettings.getInt(TAG_USER_CACHE_LIFE_MS, BasicAuthProvider.class); + if (userCacheLifeMs > 0) { + LOGGER.info("Enabling user cache."); + cache = new PassiveExpiringMap<>(userCacheLifeMs); + } } public boolean isPlainTextPassword() { @@ -113,6 +122,11 @@ private Condition passwordCondition(String passwordOrHash) { * @return true if the user is value */ public boolean isValidUser(UserData userData) { + final UserData cachedData = getFromCache(userData); + if (cachedData != null) { + userData.roles.addAll(cachedData.roles); + return true; + } maybeUpdateDatabase(); try (final ConnectionWrapper connectionProvider = new ConnectionWrapper(authSettings, connectionUrl)) { final DSLContext dslContext = DSL.using(connectionProvider.get(), SQLDialect.POSTGRES); @@ -128,13 +142,39 @@ public boolean isValidUser(UserData userData) { .stream() .filter(Objects::nonNull) .forEach(userData.roles::add); - return !roles.isEmpty(); + boolean valid = !roles.isEmpty(); + if (valid) { + addToCache(userData); + } + return valid; } catch (SQLException | RuntimeException exc) { LOGGER.error("Failed to check user credentials.", exc); return false; } } + public UserData getFromCache(UserData userData) { + if (cache == null) { + return null; + } + try { + return cache.get(userData); + } catch (RuntimeException ex) { + LOGGER.debug("Failed to check cache.", ex); + return null; + } + } + + public void addToCache(UserData userData) { + if (cache != null) { + try { + cache.put(userData, userData); + } catch (RuntimeException exc) { + LOGGER.debug("Failed to fill cache.", exc); + } + } + } + /** * This method checks if the given user exists with the given password and * has the given role. diff --git a/FROST-Server.Util/src/main/java/de/fraunhofer/iosb/ilt/frostserver/util/user/UserData.java b/FROST-Server.Util/src/main/java/de/fraunhofer/iosb/ilt/frostserver/util/user/UserData.java index 2c3e53175..e949ca9d1 100644 --- a/FROST-Server.Util/src/main/java/de/fraunhofer/iosb/ilt/frostserver/util/user/UserData.java +++ b/FROST-Server.Util/src/main/java/de/fraunhofer/iosb/ilt/frostserver/util/user/UserData.java @@ -18,6 +18,7 @@ package de.fraunhofer.iosb.ilt.frostserver.util.user; import java.util.LinkedHashSet; +import java.util.Objects; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,4 +77,30 @@ public boolean isEmpty() { return userName == null; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final UserData other = (UserData) obj; + if (!Objects.equals(this.userName, other.userName)) { + return false; + } + return Objects.equals(this.userPass, other.userPass); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 71 * hash + Objects.hashCode(this.userName); + hash = 71 * hash + Objects.hashCode(this.userPass); + return hash; + } + }