diff --git a/.config/pmd/java/ruleset.xml b/.config/pmd/java/ruleset.xml
index ebdbd83a..267fa5e9 100644
--- a/.config/pmd/java/ruleset.xml
+++ b/.config/pmd/java/ruleset.xml
@@ -42,6 +42,7 @@
+
@@ -138,6 +139,7 @@
+
@@ -155,7 +157,7 @@
-
+
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index df608bf3..6dcc9fbd 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -87,7 +87,7 @@ jobs:
- name: Create Release
id: create-release
- uses: shogo82148/actions-create-release@4661dc54f7b4b564074e9fbf73884d960de569a3 # v1
+ uses: shogo82148/actions-create-release@7b89596097b26731bda0852f1504f813499079ee # v1
with:
tag_name: v${{ steps.version.outputs.release }}
release_name: v${{ steps.version.outputs.release }}
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 6a6b8b2c..c0bcafe9 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,17 +1,3 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
+wrapperVersion=3.3.4
+distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f619fc78..f532b0da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+# 1.3.0
+* Actuator
+ * Added support for custom password hashers
+ * The default built-in password-hasher is still using `SHA-256`
+ * `ActuatorUserConfig#passwordSha256` was renamed to `passwordHash`
+ * `passwordSha256` is deprecated and will be removed in a future release
+ * Now utilizes password hash caching if possible
+ * The cache defaults to a maximum size of 100 and a cached duration of 1h
+ * Enabled when one of the following libraries is detected on the class-path:
+ * [caffeine](https://github.com/ben-manes/caffeine)
+ * [expiring-limited-cache](https://github.com/xdev-software/expiring-limited-cache)
+ * Can be disabled with `sse.sidecar.actuator.password-hash.cache.enabled` if required
+ * See source code for details configuration options
+* Updated dependencies
+
# 1.2.2
* Minor code cleanup
* Updated dependencies
diff --git a/README.md b/README.md
index 5f7d6d74..e37226cc 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ Please note that more detailed descriptions are available in the individual modu
* [web-sidecar-actuator](./web-sidecar-actuator/)
* Secures Spring Boot's Actuator
* Multi-User support
- * Allows securing different endpoint per user
+ * Allows securing different endpoints per user
* Only password hashes are stored on the server side
* [web-sidecar-common](./web-sidecar-common/)
* Host static resources without creating sessions
diff --git a/bom/pom.xml b/bom/pom.xml
index 84de2599..8d3f4a96 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
bom
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
pom
bom
@@ -51,62 +51,62 @@
software.xdev.sse
client-storage
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
crypto-symmetric
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
crypto-symmetric-managed
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
codec-sha256
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
csp
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
metrics
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
oauth2-oidc
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
oauth2-oidc-remember-me
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
vaadin
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
web
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
web-sidecar-actuator
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
web-sidecar-common
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
diff --git a/client-storage/pom.xml b/client-storage/pom.xml
index 8994f1b0..dedad276 100644
--- a/client-storage/pom.xml
+++ b/client-storage/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
client-storage
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
client-storage
@@ -261,12 +261,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/codec-sha256/pom.xml b/codec-sha256/pom.xml
index 9d4b4883..85ef6a63 100644
--- a/codec-sha256/pom.xml
+++ b/codec-sha256/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
codec-sha256
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
codec-sha256
@@ -150,7 +150,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.5.3
+ 3.5.4
@@ -268,12 +268,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/crypto-symmetric-managed/pom.xml b/crypto-symmetric-managed/pom.xml
index 1525489c..2087a8c7 100644
--- a/crypto-symmetric-managed/pom.xml
+++ b/crypto-symmetric-managed/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
crypto-symmetric-managed
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
crypto-symmetric-managed
@@ -54,7 +54,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 3.5.6
pom
import
@@ -287,12 +287,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/crypto-symmetric/pom.xml b/crypto-symmetric/pom.xml
index 10f38db7..00447e61 100644
--- a/crypto-symmetric/pom.xml
+++ b/crypto-symmetric/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
crypto-symmetric
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
crypto-symmetric
@@ -150,7 +150,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.5.3
+ 3.5.4
@@ -268,12 +268,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/csp/pom.xml b/csp/pom.xml
index e2dbaf4a..32f24823 100644
--- a/csp/pom.xml
+++ b/csp/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
csp
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
csp
@@ -54,7 +54,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 3.5.6
pom
import
@@ -277,12 +277,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/demo/entities-metamodel/pom.xml b/demo/entities-metamodel/pom.xml
index 17c2d423..9ad05ef7 100644
--- a/demo/entities-metamodel/pom.xml
+++ b/demo/entities-metamodel/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo
demo
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
entities-metamodel
diff --git a/demo/entities/pom.xml b/demo/entities/pom.xml
index 25dbd499..3624e068 100644
--- a/demo/entities/pom.xml
+++ b/demo/entities/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo
demo
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
entities
diff --git a/demo/integration-tests/pom.xml b/demo/integration-tests/pom.xml
index b479d9a2..cf5b35be 100644
--- a/demo/integration-tests/pom.xml
+++ b/demo/integration-tests/pom.xml
@@ -7,12 +7,12 @@
software.xdev.sse.demo
demo
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse.demo.it
integration-tests
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
pom
@@ -31,31 +31,31 @@
software.xdev.sse.demo.it
tci-db
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse.demo.it
tci-webapp
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse.demo.it
tci-webapp-rest
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse.demo.it
tci-webapp-vaadin
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse.demo.it
webapp-it-base
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
@@ -99,7 +99,7 @@
software.xdev.tci
bom
- 2.6.0
+ 2.7.0
pom
import
diff --git a/demo/integration-tests/tci-db/pom.xml b/demo/integration-tests/tci-db/pom.xml
index 9ccf7c34..d5e2ec44 100644
--- a/demo/integration-tests/tci-db/pom.xml
+++ b/demo/integration-tests/tci-db/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo.it
integration-tests
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
tci-db
diff --git a/demo/integration-tests/tci-webapp-rest/pom.xml b/demo/integration-tests/tci-webapp-rest/pom.xml
index 2c7bd89f..5d84b136 100644
--- a/demo/integration-tests/tci-webapp-rest/pom.xml
+++ b/demo/integration-tests/tci-webapp-rest/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo.it
integration-tests
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
tci-webapp-rest
diff --git a/demo/integration-tests/tci-webapp-vaadin/pom.xml b/demo/integration-tests/tci-webapp-vaadin/pom.xml
index d321328d..d24da608 100644
--- a/demo/integration-tests/tci-webapp-vaadin/pom.xml
+++ b/demo/integration-tests/tci-webapp-vaadin/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo.it
integration-tests
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
tci-webapp-vaadin
diff --git a/demo/integration-tests/tci-webapp/pom.xml b/demo/integration-tests/tci-webapp/pom.xml
index 8501c8f1..e2f0d7af 100644
--- a/demo/integration-tests/tci-webapp/pom.xml
+++ b/demo/integration-tests/tci-webapp/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo.it
integration-tests
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
tci-webapp
diff --git a/demo/integration-tests/tci-webapp/src/main/java/software/xdev/sse/demo/tci/webapp/containers/WebAppContainer.java b/demo/integration-tests/tci-webapp/src/main/java/software/xdev/sse/demo/tci/webapp/containers/WebAppContainer.java
index 7f204535..0579aac8 100644
--- a/demo/integration-tests/tci-webapp/src/main/java/software/xdev/sse/demo/tci/webapp/containers/WebAppContainer.java
+++ b/demo/integration-tests/tci-webapp/src/main/java/software/xdev/sse/demo/tci/webapp/containers/WebAppContainer.java
@@ -67,7 +67,7 @@ public SELF withAuth(
public SELF withActuator(final String username, final String hash)
{
return this.withEnv("SSE_ACTUATOR_USERS_0_USERNAME", username)
- .withEnv("SSE_ACTUATOR_USERS_0_PASSWORD-SHA-256", hash);
+ .withEnv("SSE_ACTUATOR_USERS_0_PASSWORD-HASH", hash);
}
public SELF withDisableHTTPS()
diff --git a/demo/integration-tests/webapp-it-base/pom.xml b/demo/integration-tests/webapp-it-base/pom.xml
index 16319d5b..2c3de8e1 100644
--- a/demo/integration-tests/webapp-it-base/pom.xml
+++ b/demo/integration-tests/webapp-it-base/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo.it
integration-tests
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
webapp-it-base
diff --git a/demo/integration-tests/webapp-it-base/src/main/java/software/xdev/sse/demo/webapp/base/AbstractBaseTest.java b/demo/integration-tests/webapp-it-base/src/main/java/software/xdev/sse/demo/webapp/base/AbstractBaseTest.java
index 2d7ce823..759a1258 100644
--- a/demo/integration-tests/webapp-it-base/src/main/java/software/xdev/sse/demo/webapp/base/AbstractBaseTest.java
+++ b/demo/integration-tests/webapp-it-base/src/main/java/software/xdev/sse/demo/webapp/base/AbstractBaseTest.java
@@ -24,6 +24,7 @@
import software.xdev.sse.demo.tci.db.factory.DBTCIFactory;
import software.xdev.sse.demo.tci.webapp.WebAppTCI;
import software.xdev.tci.TCI;
+import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
import software.xdev.tci.factory.prestart.PreStartableTCIFactory;
import software.xdev.tci.factory.registry.TCIFactoryRegistry;
import software.xdev.tci.network.LazyNetworkPool;
@@ -100,10 +101,12 @@ protected void startBaseInfrastructure(final Consumer onDataBaseMigrated)
this.network = LAZY_NETWORK_POOL.getNew();
cfOIDC = CompletableFuture.supplyAsync(
- () -> OIDC_INFRA_FACTORY.getNew(this.network, DNS_NAME_OIDC));
+ () -> OIDC_INFRA_FACTORY.getNew(this.network, DNS_NAME_OIDC),
+ TCIExecutorServiceHolder.instance());
cfApp = CompletableFuture.supplyAsync(
- () -> this.appInfraFactory.getNew(this.network, DNS_NAME_WEBAPP));
+ () -> this.appInfraFactory.getNew(this.network, DNS_NAME_WEBAPP),
+ TCIExecutorServiceHolder.instance());
this.dbInfra = DB_INFRA_FACTORY.getNew(this.network, DNS_NAME_DB);
Optional.ofNullable(onDataBaseMigrated).ifPresent(c -> c.accept(this.dbInfra));
@@ -199,7 +202,7 @@ protected void stopWebDriver()
{
LOG.warn("Failed to stop WebDriver(async)", ex);
}
- });
+ }, TCIExecutorServiceHolder.instance());
this.remoteWebDriver = null;
this.browserInfra = null;
@@ -223,7 +226,7 @@ protected void stopEverything()
Stream.of(appInfra, oidcInfra, dbInfra)
.filter(Objects::nonNull)
.map(tci -> tci::stop))
- .map(CompletableFuture::runAsync)
+ .map(r -> CompletableFuture.runAsync(r, TCIExecutorServiceHolder.instance()))
.toList() // collect so everything is getting executed async
.forEach(CompletableFuture::join);
@@ -233,7 +236,7 @@ protected void stopEverything()
{
LOG.error("Failed to stop everything(async)", ex);
}
- });
+ }, TCIExecutorServiceHolder.instance());
this.appInfra = null;
this.oidcInfra = null;
diff --git a/demo/integration-tests/webapp-rest-it/pom.xml b/demo/integration-tests/webapp-rest-it/pom.xml
index 8101e02e..faf831c2 100644
--- a/demo/integration-tests/webapp-rest-it/pom.xml
+++ b/demo/integration-tests/webapp-rest-it/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo.it
integration-tests
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
webapp-rest-it
diff --git a/demo/integration-tests/webapp-rest-it/src/test/java/software/xdev/sse/demo/rest/cases/LoginOtherTest.java b/demo/integration-tests/webapp-rest-it/src/test/java/software/xdev/sse/demo/rest/cases/LoginOtherTest.java
index 33ed4d61..3238653c 100644
--- a/demo/integration-tests/webapp-rest-it/src/test/java/software/xdev/sse/demo/rest/cases/LoginOtherTest.java
+++ b/demo/integration-tests/webapp-rest-it/src/test/java/software/xdev/sse/demo/rest/cases/LoginOtherTest.java
@@ -124,7 +124,9 @@ static Stream checkNoSessionCreatedForActuator()
ALL_SUPPORTED_HTTP_METHODS.stream()
.map(method -> Arguments.of(false, false, method, HttpStatus.SC_UNAUTHORIZED)),
// TRACE is not supported by Spring Boot
- Stream.of(Arguments.of(false, false, HttpTrace.METHOD_NAME, HttpStatus.SC_BAD_REQUEST))
+ Stream.of(false, true)
+ .map(existingPath ->
+ Arguments.of(false, existingPath, HttpTrace.METHOD_NAME, HttpStatus.SC_METHOD_NOT_ALLOWED))
).flatMap(Function.identity());
}
diff --git a/demo/integration-tests/webapp-vaadin-it/pom.xml b/demo/integration-tests/webapp-vaadin-it/pom.xml
index 002d5ac3..a6d6c441 100644
--- a/demo/integration-tests/webapp-vaadin-it/pom.xml
+++ b/demo/integration-tests/webapp-vaadin-it/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo.it
integration-tests
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
webapp-vaadin-it
diff --git a/demo/persistence/pom.xml b/demo/persistence/pom.xml
index a7a6838c..6b0ba266 100644
--- a/demo/persistence/pom.xml
+++ b/demo/persistence/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo
demo
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
persistence
diff --git a/demo/pom.xml b/demo/pom.xml
index c4b74b5e..7bdd799b 100644
--- a/demo/pom.xml
+++ b/demo/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse.demo
demo
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
pom
@@ -24,7 +24,7 @@
24.8.8
- 3.5.5
+ 3.5.6
@@ -43,25 +43,25 @@
software.xdev.sse.demo
entities
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse.demo
entities-metamodel
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse.demo
persistence
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse.demo
webapp-shared
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
@@ -91,6 +91,18 @@
3.0.1
+
+
+ software.xdev
+ expiring-limited-cache
+ 2.2.3
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 3.2.2
+
+
@@ -105,7 +117,7 @@
org.mariadb.jdbc
mariadb-java-client
- 3.5.5
+ 3.5.6
@@ -137,27 +149,27 @@
software.xdev.sse
csp
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
oauth2-oidc
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
oauth2-oidc-remember-me
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
vaadin
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
software.xdev.sse
web-sidecar-actuator
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
@@ -183,7 +195,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.5.3
+ 3.5.4
@@ -289,12 +301,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/demo/webapp-rest/pom.xml b/demo/webapp-rest/pom.xml
index acfb23a5..02f67b5d 100644
--- a/demo/webapp-rest/pom.xml
+++ b/demo/webapp-rest/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo
demo
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
webapp-rest
diff --git a/demo/webapp-rest/src/main/java/software/xdev/sse/demo/Application.java b/demo/webapp-rest/src/main/java/software/xdev/sse/demo/Application.java
index 6e8e5b66..c98f3042 100644
--- a/demo/webapp-rest/src/main/java/software/xdev/sse/demo/Application.java
+++ b/demo/webapp-rest/src/main/java/software/xdev/sse/demo/Application.java
@@ -5,11 +5,14 @@
import java.util.stream.Stream;
import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
@SuppressWarnings({"checkstyle:HideUtilityClassConstructor", "PMD.UseUtilityClass"})
@SpringBootApplication
+@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
public class Application
{
@SuppressWarnings("PMD.AvoidSystemSetterCall")
diff --git a/demo/webapp-rest/src/main/resources-dev/application-add.yml b/demo/webapp-rest/src/main/resources-dev/application-add.yml
index d8b132e8..c546ec2c 100644
--- a/demo/webapp-rest/src/main/resources-dev/application-add.yml
+++ b/demo/webapp-rest/src/main/resources-dev/application-add.yml
@@ -3,9 +3,9 @@ sse:
users:
# username = password
- username: actuator
- password-sha-256: 425edd11c26ae24d6726f66925c024ad7978400bd4ebb10bc943854ab93b3778
+ password-hash: 425edd11c26ae24d6726f66925c024ad7978400bd4ebb10bc943854ab93b3778
- username: prometheus
- password-sha-256: 1809f7cd0c75acf34f56d8c19782b99c6b5fcd14128a3cc79aca38a4f94af3ff
+ password-hash: 1809f7cd0c75acf34f56d8c19782b99c6b5fcd14128a3cc79aca38a4f94af3ff
allowed-endpoints:
- prometheus
diff --git a/demo/webapp-shared/pom.xml b/demo/webapp-shared/pom.xml
index a803c193..6cca051b 100644
--- a/demo/webapp-shared/pom.xml
+++ b/demo/webapp-shared/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo
demo
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
webapp-shared
@@ -75,6 +75,16 @@
spring-security-advanced-authentication-ui
+
+
+ software.xdev
+ expiring-limited-cache
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+
+
software.xdev.sse
diff --git a/demo/webapp-vaadin/pom.xml b/demo/webapp-vaadin/pom.xml
index 3567593d..2876c0bc 100644
--- a/demo/webapp-vaadin/pom.xml
+++ b/demo/webapp-vaadin/pom.xml
@@ -7,7 +7,7 @@
software.xdev.sse.demo
demo
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
webapp-vaadin
diff --git a/demo/webapp-vaadin/src/main/resources-dev/application-add.yml b/demo/webapp-vaadin/src/main/resources-dev/application-add.yml
index 5b2e2621..f75abeca 100644
--- a/demo/webapp-vaadin/src/main/resources-dev/application-add.yml
+++ b/demo/webapp-vaadin/src/main/resources-dev/application-add.yml
@@ -14,9 +14,9 @@ sse:
users:
# username = password
- username: actuator
- password-sha-256: 425edd11c26ae24d6726f66925c024ad7978400bd4ebb10bc943854ab93b3778
+ password-hash: 425edd11c26ae24d6726f66925c024ad7978400bd4ebb10bc943854ab93b3778
- username: prometheus
- password-sha-256: 1809f7cd0c75acf34f56d8c19782b99c6b5fcd14128a3cc79aca38a4f94af3ff
+ password-hash: 1809f7cd0c75acf34f56d8c19782b99c6b5fcd14128a3cc79aca38a4f94af3ff
allowed-endpoints:
- prometheus
diff --git a/metrics/pom.xml b/metrics/pom.xml
index 0d8afc75..e9262e27 100644
--- a/metrics/pom.xml
+++ b/metrics/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
metrics
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
metrics
@@ -54,7 +54,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 3.5.6
pom
import
@@ -272,12 +272,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/mvnw b/mvnw
index 08303327..bd8896bf 100755
--- a/mvnw
+++ b/mvnw
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Apache Maven Wrapper startup batch script, version 3.3.0
+# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
@@ -97,14 +97,25 @@ die() {
exit 1
}
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
- distributionUrl) distributionUrl="${value-}" ;;
- distributionSha256Sum) distributionSha256Sum="${value-}" ;;
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
-done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
-[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
@@ -122,7 +133,7 @@ maven-mvnd-*bin.*)
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
-*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
@@ -131,7 +142,8 @@ esac
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
-MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
@@ -199,7 +211,7 @@ elif set_java_home; then
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
- java.nio.file.Files.copy( new java.net.URL( args[0] ).openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
@@ -218,7 +230,7 @@ if [ -n "${distributionSha256Sum-}" ]; then
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
- if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
@@ -243,8 +255,41 @@ if command -v unzip >/dev/null; then
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
-printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
-mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+actualDistributionDir=""
+
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$distributionUrlNameMain"
+ fi
+fi
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if [ -z "$actualDistributionDir" ]; then
+ # enable globbing to iterate over items
+ set +f
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
+ if [ -d "$dir" ]; then
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$(basename "$dir")"
+ break
+ fi
+ fi
+ done
+ set -f
+fi
+
+if [ -z "$actualDistributionDir" ]; then
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+ die "Could not find Maven distribution directory in extracted archive"
+fi
+
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
index 136e686a..92450f93 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -19,7 +19,7 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Apache Maven Wrapper startup batch script, version 3.3.0
+@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@@ -40,7 +40,7 @@
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
-@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
@@ -73,13 +73,30 @@ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
if ($env:MVNW_REPOURL) {
- $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
- $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
-$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
-$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+
+$MAVEN_M2_PATH = "$HOME/.m2"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
+}
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
@@ -131,7 +148,33 @@ if ($distributionSha256Sum) {
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
-Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
+ $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if (!$actualDistributionDir) {
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+ if (Test-Path -Path $testPath -PathType Leaf) {
+ $actualDistributionDir = $_.Name
+ }
+ }
+}
+
+if (!$actualDistributionDir) {
+ Write-Error "Could not find Maven distribution directory in extracted archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
diff --git a/oauth2-oidc-remember-me/README.md b/oauth2-oidc-remember-me/README.md
index 7cb6d43a..015dc5c2 100644
--- a/oauth2-oidc-remember-me/README.md
+++ b/oauth2-oidc-remember-me/README.md
@@ -37,7 +37,7 @@ Spring Boot provides [Remember-Me authentication](https://docs.spring.io/spring-
This is a lot better than using serializing the session, as it doesn't requires lots of serialization and data-storage.
-However are still some problem with that:
+However there are still some problem with that:
* You need to revalidate if the client is still allowed to login
* This means communicating with the OIDC server, to get relevant information
* If the OIDC server is down you have a problem because you can't get the required data
diff --git a/oauth2-oidc-remember-me/pom.xml b/oauth2-oidc-remember-me/pom.xml
index 6d7b9977..38ab4ced 100644
--- a/oauth2-oidc-remember-me/pom.xml
+++ b/oauth2-oidc-remember-me/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
oauth2-oidc-remember-me
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
oauth2-oidc-remember-me
@@ -54,7 +54,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 3.5.6
pom
import
@@ -184,7 +184,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.5.3
+ 3.5.4
@@ -302,12 +302,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/oauth2-oidc/pom.xml b/oauth2-oidc/pom.xml
index da2d4200..95b98b13 100644
--- a/oauth2-oidc/pom.xml
+++ b/oauth2-oidc/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
oauth2-oidc
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
oauth2-oidc
@@ -54,7 +54,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 3.5.6
pom
import
@@ -203,7 +203,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.5.3
+ 3.5.4
@@ -321,12 +321,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/pom.xml b/pom.xml
index 004d37e3..950cf007 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
root
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
pom
@@ -129,12 +129,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/vaadin/pom.xml b/vaadin/pom.xml
index 98a490bc..8f18733d 100644
--- a/vaadin/pom.xml
+++ b/vaadin/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
vaadin
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
vaadin
@@ -54,7 +54,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 3.5.6
pom
import
@@ -318,12 +318,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/web-sidecar-actuator/README.md b/web-sidecar-actuator/README.md
index 2880e93f..37d152bf 100644
--- a/web-sidecar-actuator/README.md
+++ b/web-sidecar-actuator/README.md
@@ -8,6 +8,11 @@ For more details about sidecars please have a look at [``../web-sidecar-common``
* Allows for multiple users
* Allows only specific endpoints per user
* Only the password hashes are stored on the server side
+* The default built-in password-hasher is using `SHA-256`
+* Utilizes password hash caching if possible
+ * Enabled when one of the following libraries is detected on the class-path:
+ * [caffeine](https://github.com/ben-manes/caffeine)
+ * [expiring-limited-cache](https://github.com/xdev-software/expiring-limited-cache)
Example configuration:
```yml
@@ -15,10 +20,11 @@ sse:
actuator:
users:
# username = password
+ # Hash is using SHA-256
- username: actuator
- password-sha-256: 425edd11c26ae24d6726f66925c024ad7978400bd4ebb10bc943854ab93b3778
+ password-hash: 425edd11c26ae24d6726f66925c024ad7978400bd4ebb10bc943854ab93b3778
- username: prometheus
- password-sha-256: 1809f7cd0c75acf34f56d8c19782b99c6b5fcd14128a3cc79aca38a4f94af3ff
+ password-hash: 1809f7cd0c75acf34f56d8c19782b99c6b5fcd14128a3cc79aca38a4f94af3ff
allowed-endpoints:
- prometheus
```
diff --git a/web-sidecar-actuator/pom.xml b/web-sidecar-actuator/pom.xml
index 4452330a..5eba7eb9 100644
--- a/web-sidecar-actuator/pom.xml
+++ b/web-sidecar-actuator/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
web-sidecar-actuator
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
web-sidecar-actuator
@@ -54,7 +54,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 3.5.6
pom
import
@@ -87,6 +87,20 @@
spring-boot-starter-actuator
+
+
+ software.xdev
+ expiring-limited-cache
+ 2.2.3
+ provided
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 3.2.2
+ provided
+
+
org.junit.jupiter
junit-jupiter
@@ -187,7 +201,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.5.3
+ 3.5.4
@@ -305,12 +319,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/ActuatorWebSecurity.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/ActuatorWebSecurity.java
index 0486d8e0..8f62fb24 100644
--- a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/ActuatorWebSecurity.java
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/ActuatorWebSecurity.java
@@ -42,9 +42,10 @@
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
-import software.xdev.sse.codec.hash.SHA256Hashing;
import software.xdev.sse.web.sidecar.actuator.config.ActuatorSecurityConfig;
import software.xdev.sse.web.sidecar.actuator.metrics.ActuatorSecurityMetricsHandler;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.PasswordHashCache;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.hasher.PasswordHasher;
@ConditionalOnProperty(value = "sse.sidecar.actuator.enabled", matchIfMissing = true)
@@ -55,13 +56,23 @@ public class ActuatorWebSecurity
private static final Logger LOG = LoggerFactory.getLogger(ActuatorWebSecurity.class);
protected final ActuatorSecurityConfig config;
+ protected final PasswordHasher passwordHasher;
+ protected final PasswordHashCache passwordHashCache;
protected final List metricsHandlers;
public ActuatorWebSecurity(
final ActuatorSecurityConfig config,
+ final List allAvailablePasswordHashers,
+ final PasswordHashCache passwordHashCache,
final List metricsHandlers)
{
this.config = config;
+ this.passwordHasher = allAvailablePasswordHashers.stream()
+ .filter(h -> config.getPasswordHasherId().equals(h.id()))
+ .findFirst()
+ .orElseThrow(() ->
+ new IllegalStateException("Failed to find PasswordHasher for " + config.getPasswordHasherId()));
+ this.passwordHashCache = passwordHashCache;
this.metricsHandlers = metricsHandlers.stream()
.filter(ActuatorSecurityMetricsHandler::enabled)
.toList();
@@ -74,7 +85,15 @@ public SecurityFilterChain configureActuator(
final WebEndpointProperties actuatorWebEndpointProperties,
final HttpSecurity http) throws Exception
{
- LOG.info("Building SecurityFilterChain with {}", this.config);
+ LOG.info(
+ "Building SecurityFilterChain with {} [passwordHasher={},passwordHashCache={},metricHandlers={}]",
+ this.config,
+ this.passwordHasher.getClass().getSimpleName(),
+ this.passwordHashCache.getClass().getSimpleName(),
+ this.metricsHandlers.stream()
+ .map(ActuatorSecurityMetricsHandler::getClass)
+ .map(Class::getSimpleName)
+ .toList());
final Set alUserEndpoints = this.config.getUsers()
.stream()
@@ -112,7 +131,7 @@ protected AuthenticationProvider createActuatorAuthProvider()
.stream()
.map(au -> User.builder()
.username(au.getUsername())
- .password(au.getPasswordSha256())
+ .password(au.getPasswordHash())
.roles((au.getAllowedEndpoints().isEmpty()
? Stream.of(this.config.getDefaultRoleName())
: au.getAllowedEndpoints().stream().map(this::endpointToRole))
@@ -123,31 +142,24 @@ protected AuthenticationProvider createActuatorAuthProvider()
new InMemoryUserDetailsManager(users));
final int passwordMaxLength = this.config.getPasswordMaxLength();
- daoAuthenticationProvider.setPasswordEncoder(new PasswordEncoder()
- {
- @Override
- public boolean matches(final CharSequence rawPassword, final String encodedPassword)
- {
+ daoAuthenticationProvider.setPasswordEncoder((MatchingOnlyPasswordEncoder)
+ (rawPassword, encodedPassword) -> {
if(rawPassword == null || rawPassword.isEmpty() || rawPassword.length() > passwordMaxLength)
{
- ActuatorWebSecurity.this.metrics(ActuatorSecurityMetricsHandler::loginFailed);
+ this.metrics(ActuatorSecurityMetricsHandler::loginFailed);
return false;
}
- final boolean success = SHA256Hashing.hash(rawPassword.toString()).equals(encodedPassword);
- ActuatorWebSecurity.this.metrics(success
+ final boolean success = this.passwordHashCache.computeIfAbsent(
+ rawPassword.toString(),
+ this.passwordHasher::hash)
+ .equals(encodedPassword);
+ this.metrics(success
? ActuatorSecurityMetricsHandler::loginSuccess
: ActuatorSecurityMetricsHandler::loginFailed);
return success;
- }
-
- @Override
- public String encode(final CharSequence rawPassword)
- {
- return rawPassword.toString();
- }
- });
+ });
return daoAuthenticationProvider;
}
@@ -156,4 +168,17 @@ protected void metrics(final Consumer consumer)
{
this.metricsHandlers.forEach(consumer);
}
+
+ @FunctionalInterface
+ protected interface MatchingOnlyPasswordEncoder extends PasswordEncoder
+ {
+ @Override
+ boolean matches(CharSequence rawPassword, String encodedPassword);
+
+ @Override
+ default String encode(final CharSequence rawPassword)
+ {
+ return rawPassword.toString();
+ }
+ }
}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/ActuatorWebSecurityAutoConfig.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/ActuatorWebSecurityAutoConfig.java
index 2507ee69..09ac7432 100644
--- a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/ActuatorWebSecurityAutoConfig.java
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/ActuatorWebSecurityAutoConfig.java
@@ -30,6 +30,9 @@
import software.xdev.sse.web.sidecar.actuator.config.ActuatorSecurityConfig;
import software.xdev.sse.web.sidecar.actuator.metrics.ActuatorSecurityMetricsHandler;
import software.xdev.sse.web.sidecar.actuator.metrics.DefaultActuatorSecurityMetricsHandler;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.PasswordHashCache;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.UnchachedPasswordHashCache;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.hasher.sha256.DefaultSHA256PasswordHasher;
@ConditionalOnProperty(value = "sse.sidecar.actuator.enabled", matchIfMissing = true)
@@ -62,4 +65,21 @@ public ActuatorBlackHolingPathsProvider actuatorBlackHolingPathsProvider(
{
return new ActuatorBlackHolingPathsProvider(webEndpointProperties);
}
+
+ @ConditionalOnProperty(
+ value = "sse.sidecar.actuator.password-hash.hasher." + DefaultSHA256PasswordHasher.ID + ".enabled",
+ matchIfMissing = true)
+ @ConditionalOnMissingBean
+ @Bean
+ public DefaultSHA256PasswordHasher defaultSHA256PasswordHasher()
+ {
+ return new DefaultSHA256PasswordHasher();
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ public PasswordHashCache passwordHashCache()
+ {
+ return new UnchachedPasswordHashCache();
+ }
}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/passwordhash/cache/caffeine/CaffeinePasswordHashCacheAutoConfig.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/passwordhash/cache/caffeine/CaffeinePasswordHashCacheAutoConfig.java
new file mode 100644
index 00000000..dda1c994
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/passwordhash/cache/caffeine/CaffeinePasswordHashCacheAutoConfig.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.auto.passwordhash.cache.caffeine;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+import software.xdev.sse.web.sidecar.actuator.auto.ActuatorWebSecurityAutoConfig;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.PasswordHashCache;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.caffeine.CaffeinePasswordHashCache;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.caffeine.CaffeinePasswordHashCacheConfig;
+
+
+@ConditionalOnClass(name = "com.github.benmanes.caffeine.cache.Caffeine")
+@ConditionalOnProperty(value = "sse.sidecar.actuator.enabled", matchIfMissing = true)
+@ConditionalOnProperty(
+ value = "sse.sidecar.actuator.password-hash.cache.enabled",
+ matchIfMissing = true)
+@ConditionalOnProperty(
+ value = "sse.sidecar.actuator.password-hash.cache.caffeine.enabled",
+ matchIfMissing = true)
+@AutoConfiguration
+@AutoConfigureBefore(ActuatorWebSecurityAutoConfig.class)
+public class CaffeinePasswordHashCacheAutoConfig
+{
+ @ConfigurationProperties("sse.sidecar.actuator.password-hash.cache.caffeine")
+ @ConditionalOnMissingBean
+ @Bean
+ public CaffeinePasswordHashCacheConfig caffeinePasswordHashCacheConfig()
+ {
+ return new CaffeinePasswordHashCacheConfig();
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ public PasswordHashCache passwordHashCache(final CaffeinePasswordHashCacheConfig config)
+ {
+ return new CaffeinePasswordHashCache(config);
+ }
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/passwordhash/cache/expiringlimited/ExpiringLimitedPasswordHashCacheAutoConfig.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/passwordhash/cache/expiringlimited/ExpiringLimitedPasswordHashCacheAutoConfig.java
new file mode 100644
index 00000000..9a749652
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/auto/passwordhash/cache/expiringlimited/ExpiringLimitedPasswordHashCacheAutoConfig.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.auto.passwordhash.cache.expiringlimited;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+import software.xdev.sse.web.sidecar.actuator.auto.ActuatorWebSecurityAutoConfig;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.PasswordHashCache;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.expiringlimited.ExpiringLimitedPasswordHashCache;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.expiringlimited.ExpiringLimitedPasswordHashCacheConfig;
+
+
+@ConditionalOnClass(name = "software.xdev.caching.ExpiringLimitedCache")
+@ConditionalOnProperty(value = "sse.sidecar.actuator.enabled", matchIfMissing = true)
+@ConditionalOnProperty(
+ value = "sse.sidecar.actuator.password-hash.cache.enabled",
+ matchIfMissing = true)
+@ConditionalOnProperty(
+ value = "sse.sidecar.actuator.password-hash.cache.expiring-limited.enabled",
+ matchIfMissing = true)
+@AutoConfiguration
+@AutoConfigureBefore(ActuatorWebSecurityAutoConfig.class)
+public class ExpiringLimitedPasswordHashCacheAutoConfig
+{
+ @ConfigurationProperties("sse.sidecar.actuator.password-hash.cache.expiring-limited")
+ @ConditionalOnMissingBean
+ @Bean
+ public ExpiringLimitedPasswordHashCacheConfig expiringLimitedCachedSHA256PasswordHasherConfig()
+ {
+ return new ExpiringLimitedPasswordHashCacheConfig();
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ public PasswordHashCache passwordHashCache(final ExpiringLimitedPasswordHashCacheConfig config)
+ {
+ return new ExpiringLimitedPasswordHashCache(config);
+ }
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/config/ActuatorSecurityConfig.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/config/ActuatorSecurityConfig.java
index fa080dd8..c2cea844 100644
--- a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/config/ActuatorSecurityConfig.java
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/config/ActuatorSecurityConfig.java
@@ -24,6 +24,8 @@
import org.springframework.validation.annotation.Validated;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.hasher.sha256.DefaultSHA256PasswordHasher;
+
@Validated
public class ActuatorSecurityConfig
@@ -34,6 +36,9 @@ public class ActuatorSecurityConfig
@Min(1)
private int passwordMaxLength = 200;
+ @NotBlank
+ private String passwordHasherId = DefaultSHA256PasswordHasher.ID;
+
@NotNull
private Set users = new HashSet<>();
@@ -59,6 +64,16 @@ public int getPasswordMaxLength()
return this.passwordMaxLength;
}
+ public String getPasswordHasherId()
+ {
+ return this.passwordHasherId;
+ }
+
+ public void setPasswordHasherId(final String passwordHasherId)
+ {
+ this.passwordHasherId = passwordHasherId;
+ }
+
public Set getUsers()
{
return this.users;
@@ -88,6 +103,8 @@ public String toString()
+ this.defaultRoleName
+ ", passwordMaxLength="
+ this.passwordMaxLength
+ + ", passwordHasherId="
+ + this.passwordHasherId
+ ", users="
+ this.users
+ ", defaultMetricsEnabled="
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/config/ActuatorUserConfig.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/config/ActuatorUserConfig.java
index 2d2f4cf5..8fcdf429 100644
--- a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/config/ActuatorUserConfig.java
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/config/ActuatorUserConfig.java
@@ -21,6 +21,8 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
+import org.slf4j.LoggerFactory;
+
public class ActuatorUserConfig
{
@@ -28,7 +30,7 @@ public class ActuatorUserConfig
private String username;
@NotBlank
- private String passwordSha256;
+ private String passwordHash;
@NotNull
private Set allowedEndpoints = new HashSet<>(); // If empty -> ACCESS TO ALL ENDPOINTS
@@ -43,14 +45,35 @@ public void setUsername(final String username)
this.username = username;
}
+ /**
+ * @deprecated Replaced by {@link #getPasswordHash()}
+ */
+ @Deprecated(since = "1.3.0", forRemoval = true)
public String getPasswordSha256()
{
- return this.passwordSha256;
+ return this.getPasswordHash();
}
+ /**
+ * @deprecated Replaced by {@link #setPasswordHash(String)}
+ */
+ @Deprecated(since = "1.3.0", forRemoval = true)
public void setPasswordSha256(final String passwordSha256)
{
- this.passwordSha256 = passwordSha256;
+ LoggerFactory.getLogger(this.getClass())
+ .error("Detected usage of deprecated property 'passwordSha256' that will be removed soon."
+ + " Use 'passwordHash' instead!");
+ this.setPasswordHash(passwordSha256);
+ }
+
+ public String getPasswordHash()
+ {
+ return this.passwordHash;
+ }
+
+ public void setPasswordHash(final String passwordHash)
+ {
+ this.passwordHash = passwordHash;
}
public void setAllowedEndpoints(final Set allowedEndpoints)
@@ -69,7 +92,7 @@ public String toString()
return "ActuatorUserConfig ["
+ "username="
+ this.username
- + ", passwordSha256="
+ + ", passwordHash="
+ "***"
+ ", allowedEndpoints="
+ (this.allowedEndpoints.isEmpty() ? "" : this.allowedEndpoints)
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/CommonPasswordHashCacheConfig.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/CommonPasswordHashCacheConfig.java
new file mode 100644
index 00000000..3980bef7
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/CommonPasswordHashCacheConfig.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.passwordhash.cache;
+
+import java.time.Duration;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+
+import org.springframework.validation.annotation.Validated;
+
+
+@Validated
+public abstract class CommonPasswordHashCacheConfig
+{
+ @NotNull
+ private Duration expiration = Duration.ofHours(1);
+ @Min(1)
+ private int maxSize = 100;
+
+ public Duration getExpiration()
+ {
+ return this.expiration;
+ }
+
+ public void setExpiration(final Duration expiration)
+ {
+ this.expiration = expiration;
+ }
+
+ public int getMaxSize()
+ {
+ return this.maxSize;
+ }
+
+ public void setMaxSize(final int maxSize)
+ {
+ this.maxSize = maxSize;
+ }
+
+ protected String propertiesToString()
+ {
+ return "expiration="
+ + this.expiration
+ + ", maxSize="
+ + this.maxSize;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getClass().getSimpleName()
+ + " ["
+ + this.propertiesToString()
+ + "]";
+ }
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/PasswordHashCache.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/PasswordHashCache.java
new file mode 100644
index 00000000..8b1ad85a
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/PasswordHashCache.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.passwordhash.cache;
+
+import java.util.function.Function;
+
+
+public interface PasswordHashCache
+{
+ String computeIfAbsent(String input, Function compute);
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/UnchachedPasswordHashCache.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/UnchachedPasswordHashCache.java
new file mode 100644
index 00000000..055feb04
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/UnchachedPasswordHashCache.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.passwordhash.cache;
+
+import java.util.function.Function;
+
+
+public class UnchachedPasswordHashCache implements PasswordHashCache
+{
+ @Override
+ public String computeIfAbsent(final String input, final Function compute)
+ {
+ return compute.apply(input);
+ }
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/caffeine/CaffeinePasswordHashCache.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/caffeine/CaffeinePasswordHashCache.java
new file mode 100644
index 00000000..30bb9c66
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/caffeine/CaffeinePasswordHashCache.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.passwordhash.cache.caffeine;
+
+import java.util.function.Function;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.PasswordHashCache;
+
+
+public class CaffeinePasswordHashCache implements PasswordHashCache
+{
+ protected final Cache cache;
+
+ public CaffeinePasswordHashCache(final CaffeinePasswordHashCacheConfig config)
+ {
+ this.cache = Caffeine.newBuilder()
+ .softValues()
+ .maximumSize(config.getMaxSize())
+ .expireAfterAccess(config.getExpiration())
+ .build();
+ }
+
+ @Override
+ public String computeIfAbsent(final String input, final Function compute)
+ {
+ return this.cache.get(input, compute);
+ }
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/caffeine/CaffeinePasswordHashCacheConfig.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/caffeine/CaffeinePasswordHashCacheConfig.java
new file mode 100644
index 00000000..b810e626
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/caffeine/CaffeinePasswordHashCacheConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.passwordhash.cache.caffeine;
+
+import org.springframework.validation.annotation.Validated;
+
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.CommonPasswordHashCacheConfig;
+
+
+@Validated
+public class CaffeinePasswordHashCacheConfig extends CommonPasswordHashCacheConfig
+{
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/expiringlimited/ExpiringLimitedPasswordHashCache.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/expiringlimited/ExpiringLimitedPasswordHashCache.java
new file mode 100644
index 00000000..f2d27449
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/expiringlimited/ExpiringLimitedPasswordHashCache.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.passwordhash.cache.expiringlimited;
+
+import java.util.function.Function;
+
+import software.xdev.caching.ExpiringLimitedCache;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.PasswordHashCache;
+
+
+public class ExpiringLimitedPasswordHashCache implements PasswordHashCache
+{
+ protected final ExpiringLimitedCache expiringLimitedCache;
+
+ public ExpiringLimitedPasswordHashCache(final ExpiringLimitedPasswordHashCacheConfig config)
+ {
+ this.expiringLimitedCache = new ExpiringLimitedCache<>(config.getExpiration(), config.getMaxSize());
+ }
+
+ @Override
+ public String computeIfAbsent(final String input, final Function compute)
+ {
+ return this.expiringLimitedCache.computeIfAbsent(input, compute);
+ }
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/expiringlimited/ExpiringLimitedPasswordHashCacheConfig.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/expiringlimited/ExpiringLimitedPasswordHashCacheConfig.java
new file mode 100644
index 00000000..0481bdfb
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/cache/expiringlimited/ExpiringLimitedPasswordHashCacheConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.passwordhash.cache.expiringlimited;
+
+import org.springframework.validation.annotation.Validated;
+
+import software.xdev.sse.web.sidecar.actuator.passwordhash.cache.CommonPasswordHashCacheConfig;
+
+
+@Validated
+public class ExpiringLimitedPasswordHashCacheConfig extends CommonPasswordHashCacheConfig
+{
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/hasher/PasswordHasher.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/hasher/PasswordHasher.java
new file mode 100644
index 00000000..79c15c0a
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/hasher/PasswordHasher.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.passwordhash.hasher;
+
+public interface PasswordHasher
+{
+ String id();
+
+ String hash(String input);
+}
diff --git a/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/hasher/sha256/DefaultSHA256PasswordHasher.java b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/hasher/sha256/DefaultSHA256PasswordHasher.java
new file mode 100644
index 00000000..edc5607e
--- /dev/null
+++ b/web-sidecar-actuator/src/main/java/software/xdev/sse/web/sidecar/actuator/passwordhash/hasher/sha256/DefaultSHA256PasswordHasher.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.sse.web.sidecar.actuator.passwordhash.hasher.sha256;
+
+import software.xdev.sse.codec.hash.SHA256Hashing;
+import software.xdev.sse.web.sidecar.actuator.passwordhash.hasher.PasswordHasher;
+
+
+public class DefaultSHA256PasswordHasher implements PasswordHasher
+{
+ public static final String ID = "default-sha256";
+
+ @Override
+ public String id()
+ {
+ return ID;
+ }
+
+ @Override
+ public String hash(final String input)
+ {
+ return SHA256Hashing.hash(input);
+ }
+}
diff --git a/web-sidecar-actuator/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/web-sidecar-actuator/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 102a375e..7e7914a5 100644
--- a/web-sidecar-actuator/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/web-sidecar-actuator/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,2 +1,4 @@
+software.xdev.sse.web.sidecar.actuator.auto.passwordhash.cache.caffeine.CaffeinePasswordHashCacheAutoConfig
+software.xdev.sse.web.sidecar.actuator.auto.passwordhash.cache.expiringlimited.ExpiringLimitedPasswordHashCacheAutoConfig
software.xdev.sse.web.sidecar.actuator.auto.ActuatorWebSecurityAutoConfig
software.xdev.sse.web.sidecar.actuator.ActuatorWebSecurity
diff --git a/web-sidecar-common/pom.xml b/web-sidecar-common/pom.xml
index 4f3c60f6..9a3c5fdd 100644
--- a/web-sidecar-common/pom.xml
+++ b/web-sidecar-common/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
web-sidecar-common
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
web-sidecar-common
@@ -54,7 +54,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 3.5.6
pom
import
@@ -282,12 +282,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0
diff --git a/web/pom.xml b/web/pom.xml
index ce8b4a59..02ea3899 100644
--- a/web/pom.xml
+++ b/web/pom.xml
@@ -6,7 +6,7 @@
software.xdev.sse
web
- 1.2.3-SNAPSHOT
+ 1.3.0-SNAPSHOT
jar
web
@@ -54,7 +54,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 3.5.6
pom
import
@@ -272,12 +272,12 @@
net.sourceforge.pmd
pmd-core
- 7.16.0
+ 7.17.0
net.sourceforge.pmd
pmd-java
- 7.16.0
+ 7.17.0