You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/11/25 12:57:05 UTC

[isis] branch master updated: ISIS-2884: fixes invalid use of password hash matching

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 8d4b3f8  ISIS-2884: fixes invalid use of password hash matching
8d4b3f8 is described below

commit 8d4b3f8dde0e81d21613b8a22fd75ecac579c2a9
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Nov 25 13:56:53 2021 +0100

    ISIS-2884: fixes invalid use of password hash matching
    
    - also ditching SecMan's PasswordEncryptionService in favor of Spring's
    PasswordEncoder
    
    - also adding IsisModuleExtSecmanEncryptionSpring as an alternative to
    IsisModuleExtSecmanEncryptionJbcrypt
---
 .../modules/ROOT/pages/2021/2.0.0-M7/mignotes.adoc | 11 ++++
 extensions/security/secman/applib/pom.xml          |  5 ++
 .../dom/ApplicationUserRepositoryAbstract.java     | 35 +++++++------
 .../dom/mixins/ApplicationUser_updatePassword.java | 13 ++---
 .../applib/user/spi/PasswordEncryptionService.java | 33 ------------
 .../security/secman/encryption-jbcrypt/pom.xml     |  2 -
 .../IsisModuleExtSecmanEncryptionJbcrypt.java      |  4 +-
 ...crypt.java => PasswordEncoderUsingJBcrypt.java} | 23 +++++----
 .../pom.xml                                        | 15 ++----
 .../IsisModuleExtSecmanEncryptionSpring.java       | 58 ++++++++++++++++++++++
 .../authenticator/AuthenticatorSecman.java         | 24 ++++++---
 .../AuthenticatorSecmanAutoConfiguration.java      |  8 +--
 extensions/security/secman/pom.xml                 |  1 +
 .../shiro/IsisModuleExtSecmanShiroRealm.java       | 24 +++++----
 14 files changed, 152 insertions(+), 104 deletions(-)

diff --git a/antora/components/relnotes/modules/ROOT/pages/2021/2.0.0-M7/mignotes.adoc b/antora/components/relnotes/modules/ROOT/pages/2021/2.0.0-M7/mignotes.adoc
index 5bc2366..325c9f0 100644
--- a/antora/components/relnotes/modules/ROOT/pages/2021/2.0.0-M7/mignotes.adoc
+++ b/antora/components/relnotes/modules/ROOT/pages/2021/2.0.0-M7/mignotes.adoc
@@ -26,6 +26,9 @@ for non-public class member introspection.
 | `org.apache.isis.applib.jaxb.JodaTimeJaxbAdapters` 
 | moved to `org.apache.isis.valuetypes.jodatime.applib.jaxb` 
 
+| _SecMan's_ `PasswordEncryptionService` was removed
+| Using Spring's `org.springframework.security.crypto.password.PasswordEncoder` instead.
+
 |===
 
 == Configuration
@@ -44,6 +47,14 @@ isis.core.meta-model.introspector.policy=ANNOTATION_REQUIRED
 
 ----
 
+=== Password Hashing
+
+_SecMan_ provides a _Jbcrypt_ based password encryption service. 
+We added an alternative based on _Spring-Security_.
+To switch out the old vs the new replace `IsisModuleExtSecmanEncryptionJbcrypt` with 
+`IsisModuleExtSecmanEncryptionSpring`. However note, that the new password hashes are 
+not compatible with the old ones. 
+
 == Maven Artifacts
 
 `-dn5` suffix was removed from artifacts, because we migrated DataNucleus 5.x to 6.x
diff --git a/extensions/security/secman/applib/pom.xml b/extensions/security/secman/applib/pom.xml
index 341c626..8fd4aff 100644
--- a/extensions/security/secman/applib/pom.xml
+++ b/extensions/security/secman/applib/pom.xml
@@ -49,6 +49,11 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        
+        <dependency>
+		    <groupId>org.springframework.security</groupId>
+		    <artifactId>spring-security-crypto</artifactId>
+		</dependency>
 
         <dependency>
             <groupId>org.apache.isis.mavendeps</groupId>
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java
index 61a103f..db69145 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java
@@ -25,7 +25,10 @@ import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.lang.Nullable;
+import org.springframework.security.crypto.password.PasswordEncoder;
 
 import org.apache.isis.applib.query.Query;
 import org.apache.isis.applib.services.eventbus.EventBusService;
@@ -35,7 +38,6 @@ import org.apache.isis.applib.services.repository.RepositoryService;
 import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.collections._Sets;
-import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.extensions.secman.applib.SecmanConfiguration;
 import org.apache.isis.extensions.secman.applib.role.dom.ApplicationRole;
@@ -43,7 +45,6 @@ import org.apache.isis.extensions.secman.applib.tenancy.dom.ApplicationTenancy;
 import org.apache.isis.extensions.secman.applib.user.dom.mixins.ApplicationUser_lock;
 import org.apache.isis.extensions.secman.applib.user.dom.mixins.ApplicationUser_unlock;
 import org.apache.isis.extensions.secman.applib.user.events.UserCreatedEvent;
-import org.apache.isis.extensions.secman.applib.user.spi.PasswordEncryptionService;
 import org.apache.isis.extensions.secman.applib.util.RegexReplacer;
 
 import lombok.NonNull;
@@ -55,16 +56,18 @@ implements ApplicationUserRepository {
     @Inject private FactoryService factoryService;
     @Inject private RepositoryService repository;
     @Inject private SecmanConfiguration configBean;
-    @Inject private Optional<PasswordEncryptionService> passwordEncryptionService; // empty if no candidate is available
 	@Inject protected IsisConfiguration isisConfiguration;
     @Inject private EventBusService eventBusService;
     @Inject RegexReplacer regexReplacer;
 
+    //@Inject private Optional<PasswordEncryptionService> passwordEncryptionService; // empty if no candidate is available
+    @Autowired(required = false) @Qualifier("secman") PasswordEncoder passwordEncoder;
+
     @Inject private javax.inject.Provider<QueryResultsCache> queryResultsCacheProvider;
 
     private final Class<U> applicationUserClass;
 
-    protected ApplicationUserRepositoryAbstract(Class<U> applicationUserClass) {
+    protected ApplicationUserRepositoryAbstract(final Class<U> applicationUserClass) {
         this.applicationUserClass = applicationUserClass;
     }
 
@@ -145,7 +148,7 @@ implements ApplicationUserRepository {
     }
 
     @Override
-    public Collection<ApplicationUser> findByRole(ApplicationRole role) {
+    public Collection<ApplicationUser> findByRole(final ApplicationRole role) {
 
         return _NullSafe.stream(role.getUsers())
                 .collect(_Sets.toUnmodifiableSorted());
@@ -179,7 +182,7 @@ implements ApplicationUserRepository {
     // -- UPDATE USER STATE
 
     @Override
-    public void enable(ApplicationUser user) {
+    public void enable(final ApplicationUser user) {
         if(user.getStatus() != ApplicationUserStatus.UNLOCKED) {
              factoryService.mixin(ApplicationUser_unlock.class, user)
              .act();
@@ -187,7 +190,7 @@ implements ApplicationUserRepository {
     }
 
     @Override
-    public void disable(ApplicationUser user) {
+    public void disable(final ApplicationUser user) {
         if(user.getStatus() != ApplicationUserStatus.LOCKED) {
             factoryService.mixin(ApplicationUser_lock.class, user)
             .act();
@@ -195,15 +198,15 @@ implements ApplicationUserRepository {
     }
 
     @Override
-    public boolean isAdminUser(ApplicationUser user) {
+    public boolean isAdminUser(final ApplicationUser user) {
         return configBean.getAdminUserName().equals(user.getName());
     }
 
     @Override
     public ApplicationUser newUser(
-            @NonNull String username,
-            @Nullable AccountType accountType,
-            Consumer<ApplicationUser> beforePersist) {
+            @NonNull final String username,
+            @Nullable final AccountType accountType,
+            final Consumer<ApplicationUser> beforePersist) {
 
         val user = newApplicationUser();
         user.setUsername(username);
@@ -227,19 +230,15 @@ implements ApplicationUserRepository {
         if(!isPasswordFeatureEnabled(user)) {
             return false;
         }
-        val encrypter = passwordEncryptionService.orElseThrow(_Exceptions::unexpectedCodeReach);
-        user.setEncryptedPassword(encrypter.encrypt(password));
+        user.setEncryptedPassword(passwordEncoder.encode(password));
         repository.persistAndFlush(user);
         return true;
     }
 
     @Override
-    public boolean isPasswordFeatureEnabled(ApplicationUser user) {
+    public boolean isPasswordFeatureEnabled(final ApplicationUser user) {
         return user.isLocalAccount()
-                /*sonar-ignore-on*/
-                && passwordEncryptionService != null // if for any reason injection fails
-                /*sonar-ignore-off*/
-                && passwordEncryptionService.isPresent();
+                && passwordEncoder != null;
     }
 
 
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/mixins/ApplicationUser_updatePassword.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/mixins/ApplicationUser_updatePassword.java
index 0208ba1..8ac1f90 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/mixins/ApplicationUser_updatePassword.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/mixins/ApplicationUser_updatePassword.java
@@ -19,21 +19,22 @@
 package org.apache.isis.extensions.secman.applib.user.dom.mixins;
 
 import java.util.Objects;
-import java.util.Optional;
 
 import javax.inject.Inject;
 
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
 import org.apache.isis.applib.annotation.Action;
 import org.apache.isis.applib.annotation.ActionLayout;
 import org.apache.isis.applib.annotation.MemberSupport;
 import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.value.Password;
-import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.extensions.secman.applib.IsisModuleExtSecmanApplib;
 import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUser;
 import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserRepository;
 import org.apache.isis.extensions.secman.applib.user.dom.mixins.ApplicationUser_updatePassword.DomainEvent;
-import org.apache.isis.extensions.secman.applib.user.spi.PasswordEncryptionService;
 
 import lombok.RequiredArgsConstructor;
 import lombok.val;
@@ -53,7 +54,7 @@ public class ApplicationUser_updatePassword {
             extends IsisModuleExtSecmanApplib.ActionDomainEvent<ApplicationUser_updatePassword> {}
 
     @Inject private ApplicationUserRepository applicationUserRepository;
-    @Inject private Optional<PasswordEncryptionService> passwordEncryptionService; // empty if no candidate is available
+    @Autowired(required = false) private @Qualifier("secman") PasswordEncoder passwordEncoder;
 
     private final ApplicationUser target;
 
@@ -87,12 +88,12 @@ public class ApplicationUser_updatePassword {
             return "Password feature is not available for this User";
         }
 
-        val encrypter = passwordEncryptionService.orElseThrow(_Exceptions::unexpectedCodeReach);
+        Objects.requireNonNull(passwordEncoder);
 
         val encryptedPassword = target.getEncryptedPassword();
 
         if(target.getEncryptedPassword() != null) {
-            if (!encrypter.matches(existingPassword.getPassword(), encryptedPassword)) {
+            if (!passwordEncoder.matches(existingPassword.getPassword(), encryptedPassword)) {
                 return "Existing password is incorrect";
             }
         }
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/spi/PasswordEncryptionService.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/spi/PasswordEncryptionService.java
deleted file mode 100644
index a6c25f0..0000000
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/spi/PasswordEncryptionService.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- *  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.
- */
-package org.apache.isis.extensions.secman.applib.user.spi;
-
-import org.apache.isis.applib.annotation.Programmatic;
-
-/**
- * @since 2.0 {@index}
- */
-public interface PasswordEncryptionService {
-
-    @Programmatic
-    public String encrypt(final String password);
-
-    @Programmatic
-    public boolean matches(final String candidate, final String encrypted);
-}
diff --git a/extensions/security/secman/encryption-jbcrypt/pom.xml b/extensions/security/secman/encryption-jbcrypt/pom.xml
index 8e20131..525133c 100644
--- a/extensions/security/secman/encryption-jbcrypt/pom.xml
+++ b/extensions/security/secman/encryption-jbcrypt/pom.xml
@@ -34,8 +34,6 @@
     <properties>
         <jar-plugin.automaticModuleName>org.apache.isis.extensions.secman.encryption.jbcrypt</jar-plugin.automaticModuleName>
         <git-plugin.propertiesDir>org/apache/isis/extensions/secman/encryption/jbcrypt</git-plugin.propertiesDir>
-
-        <jbcrypt.version>0.4</jbcrypt.version>
     </properties>
 
     <dependencies>
diff --git a/extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/IsisModuleExtSecmanEncryptionJbcrypt.java b/extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/IsisModuleExtSecmanEncryptionJbcrypt.java
index 903c112..24f1e31 100644
--- a/extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/IsisModuleExtSecmanEncryptionJbcrypt.java
+++ b/extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/IsisModuleExtSecmanEncryptionJbcrypt.java
@@ -21,14 +21,14 @@ package org.apache.isis.extensions.secman.encryption.jbcrypt;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
-import org.apache.isis.extensions.secman.encryption.jbcrypt.services.PasswordEncryptionServiceUsingJBcrypt;
+import org.apache.isis.extensions.secman.encryption.jbcrypt.services.PasswordEncoderUsingJBcrypt;
 
 /**
  * @since 2.0 {@index}
  */
 @Configuration
 @Import({
-    PasswordEncryptionServiceUsingJBcrypt.class
+    PasswordEncoderUsingJBcrypt.class
 })
 public class IsisModuleExtSecmanEncryptionJbcrypt {
 
diff --git a/extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/services/PasswordEncryptionServiceUsingJBcrypt.java b/extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/services/PasswordEncoderUsingJBcrypt.java
similarity index 69%
rename from extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/services/PasswordEncryptionServiceUsingJBcrypt.java
rename to extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/services/PasswordEncoderUsingJBcrypt.java
index 5499ab1..f00bbfd 100644
--- a/extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/services/PasswordEncryptionServiceUsingJBcrypt.java
+++ b/extensions/security/secman/encryption-jbcrypt/src/main/java/org/apache/isis/extensions/secman/encryption/jbcrypt/services/PasswordEncoderUsingJBcrypt.java
@@ -20,21 +20,22 @@ package org.apache.isis.extensions.secman.encryption.jbcrypt.services;
 
 import javax.inject.Named;
 
-import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.mindrot.jbcrypt.BCrypt;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 
-import org.apache.isis.extensions.secman.applib.user.spi.PasswordEncryptionService;
+import org.apache.isis.applib.annotation.PriorityPrecedence;
 
 /**
  * @since 2.0 {@index}
  */
 @Service
-@Named("isis.ext.secman.PasswordEncryptionServiceUsingJBcrypt")
+@Named("isis.ext.secman.PasswordEncoderUsingJBcrypt")
 @javax.annotation.Priority(PriorityPrecedence.MIDPOINT)
-@Qualifier("JBCrypt")
-public class PasswordEncryptionServiceUsingJBcrypt implements PasswordEncryptionService {
+@Qualifier("secman")
+public class PasswordEncoderUsingJBcrypt
+implements PasswordEncoder {
 
     private String salt;
 
@@ -46,18 +47,18 @@ public class PasswordEncryptionServiceUsingJBcrypt implements PasswordEncryption
     }
 
     @Override
-    public String encrypt(String password) {
-        return password == null ? null : BCrypt.hashpw(password, getSalt());
+    public String encode(final CharSequence rawPassword) {
+        return rawPassword == null ? null : BCrypt.hashpw(rawPassword.toString(), getSalt());
     }
 
     @Override
-    public boolean matches(String candidate, String encrypted) {
-        if (candidate == null && encrypted == null) {
+    public boolean matches(final CharSequence rawPassword, final String encodedPassword) {
+        if (rawPassword == null && encodedPassword == null) {
             return true;
         }
-        if (candidate == null || encrypted == null) {
+        if (rawPassword == null || encodedPassword == null) {
             return false;
         }
-        return BCrypt.checkpw(candidate, encrypted);
+        return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
     }
 }
diff --git a/extensions/security/secman/encryption-jbcrypt/pom.xml b/extensions/security/secman/encryption-spring/pom.xml
similarity index 81%
copy from extensions/security/secman/encryption-jbcrypt/pom.xml
copy to extensions/security/secman/encryption-spring/pom.xml
index 8e20131..2b4f814 100644
--- a/extensions/security/secman/encryption-jbcrypt/pom.xml
+++ b/extensions/security/secman/encryption-spring/pom.xml
@@ -27,15 +27,13 @@
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <artifactId>isis-extensions-secman-encryption-jbcrypt</artifactId>
-    <name>Apache Isis Ext - Sec Man Encryption (Using jbcrypt)</name>
+    <artifactId>isis-extensions-secman-encryption-spring</artifactId>
+    <name>Apache Isis Ext - Sec Man Encryption (Using Spring)</name>
     <description></description>
 
     <properties>
-        <jar-plugin.automaticModuleName>org.apache.isis.extensions.secman.encryption.jbcrypt</jar-plugin.automaticModuleName>
-        <git-plugin.propertiesDir>org/apache/isis/extensions/secman/encryption/jbcrypt</git-plugin.propertiesDir>
-
-        <jbcrypt.version>0.4</jbcrypt.version>
+        <jar-plugin.automaticModuleName>org.apache.isis.extensions.secman.encryption.spring</jar-plugin.automaticModuleName>
+        <git-plugin.propertiesDir>org/apache/isis/extensions/secman/encryption/spring</git-plugin.propertiesDir>
     </properties>
 
     <dependencies>
@@ -52,11 +50,6 @@
 			<scope>provided</scope>
 		</dependency>
 
-		<dependency>
-            <groupId>org.mindrot</groupId>
-            <artifactId>jbcrypt</artifactId>
-        </dependency>
-
     </dependencies>
 
 </project>
diff --git a/extensions/security/secman/encryption-spring/src/main/java/org/apache/isis/extensions/secman/encryption/spring/IsisModuleExtSecmanEncryptionSpring.java b/extensions/security/secman/encryption-spring/src/main/java/org/apache/isis/extensions/secman/encryption/spring/IsisModuleExtSecmanEncryptionSpring.java
new file mode 100644
index 0000000..90be1aa
--- /dev/null
+++ b/extensions/security/secman/encryption-spring/src/main/java/org/apache/isis/extensions/secman/encryption/spring/IsisModuleExtSecmanEncryptionSpring.java
@@ -0,0 +1,58 @@
+/*
+ *  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.
+ */
+package org.apache.isis.extensions.secman.encryption.spring;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
+
+import lombok.val;
+
+/**
+ * @since 2.0 {@index}
+ */
+@Configuration
+@Import({
+
+})
+public class IsisModuleExtSecmanEncryptionSpring {
+
+    /**
+     * @see "https://www.baeldung.com/spring-security-5-default-password-encoder"
+     */
+    @Bean @Qualifier("secman") @Order(Ordered.LOWEST_PRECEDENCE)
+    public PasswordEncoder passwordEncoder() {
+        // set up the list of supported encoders and their prefixes
+        val encoders = Map.<String, PasswordEncoder>of(
+                "bcrypt", new BCryptPasswordEncoder(),
+                "scrypt", new SCryptPasswordEncoder());
+
+        return new DelegatingPasswordEncoder("bcrypt", encoders);
+    }
+
+}
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecman.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecman.java
index 052a62d..a6bcf42 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecman.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecman.java
@@ -22,6 +22,9 @@ import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
 import org.apache.isis.applib.services.iactnlayer.InteractionContext;
 import org.apache.isis.applib.services.user.UserMemento;
 import org.apache.isis.core.security.authentication.AuthenticationRequest;
@@ -30,9 +33,7 @@ import org.apache.isis.core.security.authentication.Authenticator;
 import org.apache.isis.extensions.secman.applib.role.dom.ApplicationRole;
 import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUser;
 import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserRepository;
-import org.apache.isis.extensions.secman.applib.user.spi.PasswordEncryptionService;
 
-import lombok.RequiredArgsConstructor;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
@@ -50,11 +51,19 @@ import lombok.extern.log4j.Log4j2;
  * @since 2.0 {@index}
  */
 @Log4j2
-@RequiredArgsConstructor(onConstructor_ = {@Inject})
+//@RequiredArgsConstructor(onConstructor_ = {@Inject})
 public class AuthenticatorSecman implements Authenticator {
 
     private final ApplicationUserRepository applicationUserRepository;
-    private final PasswordEncryptionService passwordEncryptionService;
+    private final PasswordEncoder passwordEncoder;
+
+    @Inject
+    public AuthenticatorSecman(
+            final ApplicationUserRepository applicationUserRepository,
+            final @Qualifier("secman") PasswordEncoder passwordEncoder) {
+        this.applicationUserRepository = applicationUserRepository;
+        this.passwordEncoder = passwordEncoder;
+    }
 
     @Override
     public final boolean canAuthenticate(final Class<? extends AuthenticationRequest> authenticationRequestClass) {
@@ -65,15 +74,14 @@ public class AuthenticatorSecman implements Authenticator {
     public InteractionContext authenticate(final AuthenticationRequest request, final String code) {
         val authRequest = (AuthenticationRequestPassword) request;
         val username = authRequest.getName();
-        val password = authRequest.getPassword();
-        val encryptedPassword = passwordEncryptionService.encrypt(password);
+        val rawPassword = authRequest.getPassword();
         if(username == null) {
             log.info("login failed: username is null");
             return null;
         }
 
         return applicationUserRepository.findByUsername(username)
-                .filter(appUser -> appUser.getEncryptedPassword().equals(encryptedPassword))
+                .filter(appUser -> passwordEncoder.matches(rawPassword, appUser.getEncryptedPassword()))
                 .map(appUser -> {
                     val roleNames = Stream.concat(
                             appUser.getRoles().stream().map(ApplicationRole::getName),
@@ -93,4 +101,6 @@ public class AuthenticatorSecman implements Authenticator {
         // be re-authenticated.
     }
 
+
+
 }
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecmanAutoConfiguration.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecmanAutoConfiguration.java
index 2e39f75..566cf50 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecmanAutoConfiguration.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authenticator/AuthenticatorSecmanAutoConfiguration.java
@@ -18,16 +18,16 @@
  */
 package org.apache.isis.extensions.secman.integration.authenticator;
 
-import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.AutoConfigureOrder;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.password.PasswordEncoder;
 
+import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.apache.isis.core.security.authentication.Authenticator;
 import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserRepository;
-import org.apache.isis.extensions.secman.applib.user.spi.PasswordEncryptionService;
 
 /**
  * @since 2.0 {@index}
@@ -41,8 +41,8 @@ public class AuthenticatorSecmanAutoConfiguration  {
     @Qualifier("Secman")
     public Authenticator authenticatorSecman(
             final ApplicationUserRepository applicationUserRepository,
-            final PasswordEncryptionService passwordEncryptionService) {
-        return new AuthenticatorSecman(applicationUserRepository, passwordEncryptionService);
+            final @Qualifier("secman") PasswordEncoder passwordEncoder) {
+        return new AuthenticatorSecman(applicationUserRepository, passwordEncoder);
     }
 
 }
diff --git a/extensions/security/secman/pom.xml b/extensions/security/secman/pom.xml
index 6ddedf2..4e9737d 100644
--- a/extensions/security/secman/pom.xml
+++ b/extensions/security/secman/pom.xml
@@ -102,6 +102,7 @@
     	<module>applib</module>
     	<module>integration</module>
     	<module>encryption-jbcrypt</module>
+    	<module>encryption-spring</module>
     	<module>persistence-jdo</module>
     	<module>persistence-jpa</module>
     	<module>shiro-realm</module>
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanShiroRealm.java b/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanShiroRealm.java
index d8866e4..ab50f49 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanShiroRealm.java
+++ b/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanShiroRealm.java
@@ -34,6 +34,9 @@ import org.apache.shiro.authz.AuthorizationInfo;
 import org.apache.shiro.realm.AuthenticatingRealm;
 import org.apache.shiro.realm.AuthorizingRealm;
 import org.apache.shiro.subject.PrincipalCollection;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.support.TransactionTemplate;
 
@@ -47,7 +50,6 @@ import org.apache.isis.core.security.authorization.Authorizor;
 import org.apache.isis.extensions.secman.applib.SecmanConfiguration;
 import org.apache.isis.extensions.secman.applib.user.dom.AccountType;
 import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserRepository;
-import org.apache.isis.extensions.secman.applib.user.spi.PasswordEncryptionService;
 import org.apache.isis.extensions.secman.shiro.util.ShiroUtils;
 
 import lombok.Getter;
@@ -85,7 +87,7 @@ public class IsisModuleExtSecmanShiroRealm extends AuthorizingRealm {
      * invalid password.
      */
     @Override
-    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+    protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token) throws AuthenticationException {
         if (!(token instanceof UsernamePasswordToken)) {
             throw new AuthenticationException();
         }
@@ -157,7 +159,7 @@ public class IsisModuleExtSecmanShiroRealm extends AuthorizingRealm {
     }
 
     @Override
-    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+    protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
         return principals.oneByType(PrincipalForApplicationUser.class);
     }
 
@@ -174,7 +176,7 @@ public class IsisModuleExtSecmanShiroRealm extends AuthorizingRealm {
      * @return {@code null} if not applicable
      */
     private PrincipalForApplicationUser getPrincipal_fromAlreadyAuthenticatedSubjectIfApplicable(
-            AuthenticationToken token) {
+            final AuthenticationToken token) {
 
         // this optimization is only implemented for the simple case of a single realm setup
         if(!ShiroUtils.isSingleRealm()) {
@@ -198,7 +200,7 @@ public class IsisModuleExtSecmanShiroRealm extends AuthorizingRealm {
         return null;
     }
 
-    private DisabledAccountException disabledAccountException(String username) {
+    private DisabledAccountException disabledAccountException(final String username) {
         return new DisabledAccountException(String.format("username='%s'", username));
     }
 
@@ -215,7 +217,7 @@ public class IsisModuleExtSecmanShiroRealm extends AuthorizingRealm {
         };
     }
 
-    private void authenticateElseThrow_usingDelegatedMechanism(AuthenticationToken token) {
+    private void authenticateElseThrow_usingDelegatedMechanism(final AuthenticationToken token) {
         AuthenticationInfo delegateAccount = null;
         try {
             delegateAccount = delegateAuthenticationRealm.getAuthenticationInfo(token);
@@ -260,17 +262,19 @@ public class IsisModuleExtSecmanShiroRealm extends AuthorizingRealm {
 
     private CheckPasswordResult checkPassword(final char[] candidate, final String actualEncryptedPassword) {
         return execute(new Supplier<CheckPasswordResult>() {
+
+            @Autowired(required = false) private @Qualifier("secman") PasswordEncoder passwordEncoder;
+
             @Override
             public CheckPasswordResult get() {
-                if (passwordEncryptionService == null) {
+                if (passwordEncoder == null) {
                     return CheckPasswordResult.NO_PASSWORD_ENCRYPTION_SERVICE_CONFIGURED;
                 }
-                return passwordEncryptionService.matches(new String(candidate), actualEncryptedPassword)
+                return passwordEncoder.matches(new String(candidate), actualEncryptedPassword)
                         ? CheckPasswordResult.OK
-                                : CheckPasswordResult.BAD_PASSWORD;
+                        : CheckPasswordResult.BAD_PASSWORD;
             }
 
-            @Inject private PasswordEncryptionService passwordEncryptionService;
         });
     }