diff options
author | Thomas <> | 2022-09-29 14:03:44 +0200 |
---|---|---|
committer | Thomas <> | 2022-09-29 14:03:44 +0200 |
commit | 28f4e988d7e7f7d2aea76d4d4667d9f7106bf5b1 (patch) | |
tree | e4952efa59a3e3ed1d971c93c28bb71302b9d3f8 | |
parent | d33fcbca761e07ca87c004e45565bf97268d496e (diff) | |
download | EAAF-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
4 files changed, 146 insertions, 1 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)); + + } + +} @@ -458,7 +458,12 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>${spring-boot-starter-web.version}</version> - </dependency> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + <version>${spring-boot-starter-web.version}</version> + </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> |