summaryrefslogtreecommitdiff
path: root/eaaf-springboot-utils
diff options
context:
space:
mode:
authorThomas <>2022-09-29 14:03:44 +0200
committerThomas <>2022-09-29 14:03:44 +0200
commit28f4e988d7e7f7d2aea76d4d4667d9f7106bf5b1 (patch)
treee4952efa59a3e3ed1d971c93c28bb71302b9d3f8 /eaaf-springboot-utils
parentd33fcbca761e07ca87c004e45565bf97268d496e (diff)
downloadEAAF-Components-28f4e988d7e7f7d2aea76d4d4667d9f7106bf5b1.tar.gz
EAAF-Components-28f4e988d7e7f7d2aea76d4d4667d9f7106bf5b1.tar.bz2
EAAF-Components-28f4e988d7e7f7d2aea76d4d4667d9f7106bf5b1.zip
feat(spring-sec): add PasswordEncorder decorator to speed-up password validation on REST API's
Diffstat (limited to 'eaaf-springboot-utils')
-rw-r--r--eaaf-springboot-utils/pom.xml5
-rw-r--r--eaaf-springboot-utils/src/main/java/at/gv/egiz/eaaf/utils/springboot/utils/CachingPasswordEncoder.java81
-rw-r--r--eaaf-springboot-utils/src/test/java/at/gv/egiz/eaaf/utils/springboot/test/utils/CachingPasswordEncoderTest.java54
3 files changed, 140 insertions, 0 deletions
diff --git a/eaaf-springboot-utils/pom.xml b/eaaf-springboot-utils/pom.xml
index 479b6c66..eeb68abe 100644
--- a/eaaf-springboot-utils/pom.xml
+++ b/eaaf-springboot-utils/pom.xml
@@ -53,6 +53,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
diff --git a/eaaf-springboot-utils/src/main/java/at/gv/egiz/eaaf/utils/springboot/utils/CachingPasswordEncoder.java b/eaaf-springboot-utils/src/main/java/at/gv/egiz/eaaf/utils/springboot/utils/CachingPasswordEncoder.java
new file mode 100644
index 00000000..672ebb5e
--- /dev/null
+++ b/eaaf-springboot-utils/src/main/java/at/gv/egiz/eaaf/utils/springboot/utils/CachingPasswordEncoder.java
@@ -0,0 +1,81 @@
+package at.gv.egiz.eaaf.utils.springboot.utils;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Decorator for {@link PasswordEncoder} that caches valid password to speed-up password verification.
+ *
+ * @author tlenz
+ *
+ */
+@Slf4j
+@AllArgsConstructor
+public class CachingPasswordEncoder implements PasswordEncoder {
+
+ /**
+ * Cryptographic password encoder.
+ */
+ private final PasswordEncoder internalEncoder;
+
+ /**
+ * Look-up table to speed-up password validation on REST API's.
+ */
+ private final Map<String, String> passwordCache = new ConcurrentHashMap<>(10);
+
+ @Override
+ public String encode(CharSequence rawPassword) {
+ log.trace("Delegate password encoding to: {}", internalEncoder.getClass().getName());
+ return internalEncoder.encode(rawPassword);
+
+ }
+
+ @Override
+ public boolean matches(CharSequence rawPassword, String encodedPassword) {
+
+ if (passwordCache.containsKey(rawPassword.toString())) {
+ log.trace("Find password in cache. Starting verification ... ");
+ String cachedPassword = passwordCache.get(rawPassword).toString();
+ if (equalsNoEarlyReturn(cachedPassword, encodedPassword)) {
+ return true;
+
+ }
+ }
+
+ return checkPasswordAndUpdateCache(rawPassword, encodedPassword);
+
+ }
+
+ @Override
+ public boolean upgradeEncoding(String encodedPassword) {
+ return internalEncoder.upgradeEncoding(encodedPassword);
+
+ }
+
+
+ private boolean checkPasswordAndUpdateCache(CharSequence rawPassword, String encodedPassword) {
+ log.trace("Password not cached. Starting password processing ... ");
+ boolean passwordValid = internalEncoder.matches(rawPassword, encodedPassword);
+ if (passwordValid) {
+ log.debug("Get valid password. Cached it for faster reusage");
+ passwordCache.put(rawPassword.toString(), encodedPassword);
+
+ }
+
+ return passwordValid;
+
+ }
+
+ static boolean equalsNoEarlyReturn(String a, String b) {
+ return MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8));
+
+ }
+
+}
diff --git a/eaaf-springboot-utils/src/test/java/at/gv/egiz/eaaf/utils/springboot/test/utils/CachingPasswordEncoderTest.java b/eaaf-springboot-utils/src/test/java/at/gv/egiz/eaaf/utils/springboot/test/utils/CachingPasswordEncoderTest.java
new file mode 100644
index 00000000..bae19cac
--- /dev/null
+++ b/eaaf-springboot-utils/src/test/java/at/gv/egiz/eaaf/utils/springboot/test/utils/CachingPasswordEncoderTest.java
@@ -0,0 +1,54 @@
+package at.gv.egiz.eaaf.utils.springboot.test.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import at.gv.egiz.eaaf.utils.springboot.utils.CachingPasswordEncoder;
+
+@RunWith(BlockJUnit4ClassRunner.class)
+public class CachingPasswordEncoderTest {
+
+ PasswordEncoder orgEncoder = new BCryptPasswordEncoder();
+ PasswordEncoder testEncoder = new CachingPasswordEncoder(orgEncoder);
+
+ @Test
+ public void upgradeEncoding() {
+ String enc = new BCryptPasswordEncoder().encode(RandomStringUtils.randomAlphabetic(5));
+
+ assertEquals("upgradeEncoding",
+ orgEncoder.upgradeEncoding(enc),
+ testEncoder.upgradeEncoding(enc));
+
+ }
+
+ @Test
+ public void encodePassword() {
+ String plain = RandomStringUtils.randomAlphabetic(5);
+ assertNotNull("password encoded", testEncoder.encode(plain));
+
+ }
+
+ @Test
+ public void matchPassword() {
+ String plain = RandomStringUtils.randomAlphabetic(5);
+ String encoded = testEncoder.encode(plain);
+
+ assertTrue("valid password", testEncoder.matches(plain, encoded));
+ assertTrue("valid password again", testEncoder.matches(plain, encoded));
+
+ assertFalse("password in cache, but changed on server",
+ testEncoder.matches(plain, RandomStringUtils.randomAlphabetic(10)));
+ assertFalse("wrong password send", testEncoder.matches(RandomStringUtils.randomAlphabetic(5), encoded));
+
+ }
+
+}