You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2022/04/13 09:11:43 UTC

[syncope] branch master updated: [SYNCOPE-1673] DefaultPasswordRule and DefaultPasswordGenerator now b… (#338)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 4d9dcb63ce [SYNCOPE-1673] DefaultPasswordRule and DefaultPasswordGenerator now b… (#338)
4d9dcb63ce is described below

commit 4d9dcb63ce89ecdc33f544f7e5e937786e354599
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Wed Apr 13 11:11:38 2022 +0200

    [SYNCOPE-1673] DefaultPasswordRule and DefaultPasswordGenerator now b… (#338)
---
 .../console/panels/ProvisionAuxClassesPanel.java   |   2 +-
 .../syncope/client/console/topology/Topology.java  |   2 +-
 .../client/console/SyncopeConsoleSession.java      |   2 +-
 .../notifications/NotificationWizardBuilder.java   |   8 +-
 .../panels/search/AnyObjectSearchPanel.java        |  16 +-
 .../console/panels/search/GroupSearchPanel.java    |   4 +-
 .../client/console/rest/SchemaRestClient.java      |   2 +-
 .../wizards/any/AbstractAttrsWizardStep.java       |   2 +-
 .../client/console/wizards/any/Relationships.java  |   2 +-
 .../client/enduser/panels/any/AbstractAttrs.java   |   2 +-
 .../client/enduser/rest/SchemaRestClient.java      |   2 +-
 .../common/lib/policy/DefaultPasswordRuleConf.java | 253 +++----------------
 .../syncope/core/logic/ReconciliationLogic.java    |   4 +-
 .../apache/syncope/core/logic/ResourceLogic.java   |   4 +-
 .../apache/syncope/core/rest/cxf/JavaDocUtils.java |   2 +-
 .../src/test/resources/domains/MasterContent.xml   |   8 +-
 .../persistence/jpa/PersistenceTestContext.java    |   2 +-
 .../core/persistence/jpa/inner/UserTest.java       |  15 +-
 .../src/test/resources/domains/MasterContent.xml   |   6 +-
 .../provisioning/java/ConnectorFacadeProxy.java    |   2 +-
 .../java/DefaultConnIdBundleManager.java           |   4 +-
 .../provisioning/java/DefaultMappingManager.java   |   7 +-
 .../AbstractPropagationTaskExecutor.java           |   2 +-
 .../java/pushpull/AbstractPushResultHandler.java   |   4 +-
 .../java/pushpull/OutboundMatcher.java             |   4 +-
 .../java/pushpull/PullJobDelegate.java             |   4 +-
 .../java/pushpull/SinglePullJobDelegate.java       |   2 +-
 .../pushpull/stream/StreamPullJobDelegate.java     |   2 +-
 .../provisioning/java/utils/ConnObjectUtils.java   |  12 +-
 core/spring/pom.xml                                |   5 +
 .../core/spring/policy/DefaultPasswordRule.java    | 189 +++++++-------
 .../spring/policy/InvalidPasswordRuleConf.java     |  37 ---
 .../spring/security/DefaultPasswordGenerator.java  | 272 ++++-----------------
 .../core/spring/security/PasswordGenerator.java    |   6 +-
 .../core/spring/security/SecureRandomUtils.java    |   7 +
 .../core/spring/ImplementationManagerTest.java     |  21 +-
 .../spring/security/PasswordGeneratorTest.java     | 186 +++++---------
 .../core/spring/security/TestPasswordPolicy.java   |   5 +-
 .../fit/core/reference/ITImplementationLookup.java |  83 ++-----
 .../org/apache/syncope/fit/AbstractITCase.java     |   2 +-
 pom.xml                                            |   6 +
 .../org/apache/syncope/sra/SecurityConfig.java     |   4 +-
 .../security/pac4j/ServerWebExchangeContext.java   |   2 +-
 .../reference-guide/concepts/policies.adoc         |  41 ++--
 44 files changed, 360 insertions(+), 887 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ProvisionAuxClassesPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ProvisionAuxClassesPanel.java
index 377a8c4b4e..d2844bb8b4 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ProvisionAuxClassesPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ProvisionAuxClassesPanel.java
@@ -102,7 +102,7 @@ public class ProvisionAuxClassesPanel extends Panel {
         classes.addAll(anyTypeClasses);
 
         return SchemaRestClient.<PlainSchemaTO>getSchemas(
-                SchemaType.PLAIN, null, classes.toArray(new String[] {})).
+                SchemaType.PLAIN, null, classes.toArray(String[]::new)).
                 stream().map(EntityTO::getKey).collect(Collectors.toList());
     }
 }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
index b152db8d04..96377e0441 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
@@ -548,7 +548,7 @@ public class Topology extends BasePage {
             }
         });
 
-        panel.add(behaviors.toArray(new Behavior[] {}));
+        panel.add(behaviors.toArray(Behavior[]::new));
 
         return panel;
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
index 35045f0d83..3292c2c667 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
@@ -338,7 +338,7 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
     @Override
     public Roles getRoles() {
         if (isSignedIn() && roles == null && auth != null) {
-            roles = new Roles(auth.keySet().toArray(new String[] {}));
+            roles = new Roles(auth.keySet().toArray(String[]::new));
             roles.add(Constants.ROLE_AUTHENTICATED);
         }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
index 98a276204a..ecc23d1cd5 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
@@ -166,7 +166,7 @@ public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<Notificatio
             add(new EventCategoryPanel(
                     "eventSelection",
                     AuditRestClient.listEvents(),
-                new PropertyModel<>(modelObject.getInnerObject(), "events")) {
+                    new PropertyModel<>(modelObject.getInnerObject(), "events")) {
 
                 private static final long serialVersionUID = 6429053774964787735L;
 
@@ -332,7 +332,7 @@ public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<Notificatio
             @Override
             protected List<String> load() {
                 return ImplementationRestClient.list(IdRepoImplementationType.RECIPIENTS_PROVIDER).stream().
-                    map(EntityTO::getKey).sorted().collect(Collectors.toList());
+                        map(EntityTO::getKey).sorted().collect(Collectors.toList());
             }
         };
 
@@ -389,8 +389,8 @@ public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<Notificatio
             LOG.error("While reading all any types", e);
         }
 
-        String[] anyTypeClasses = Optional.ofNullable(type)
-                .map(anyTypeTO -> anyTypeTO.getClasses().toArray(new String[] {})).orElseGet(() -> new String[0]);
+        String[] anyTypeClasses = Optional.ofNullable(type).
+                map(anyTypeTO -> anyTypeTO.getClasses().toArray(String[]::new)).orElseGet(() -> new String[0]);
 
         List<String> result = new ArrayList<>();
         result.add(Constants.USERNAME_FIELD_NAME);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java
index d1c22c6de0..80235038aa 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java
@@ -86,12 +86,12 @@ public class AnyObjectSearchPanel extends AbstractSearchPanel {
             @Override
             protected List<String> load() {
                 return groupRestClient.search(
-                    SyncopeConstants.ROOT_REALM,
-                    null,
-                    1,
-                    Constants.MAX_GROUP_LIST_SIZE,
-                    new SortParam<>(Constants.NAME_FIELD_NAME, true),
-                    null).stream().map(GroupTO::getName).collect(Collectors.toList());
+                        SyncopeConstants.ROOT_REALM,
+                        null,
+                        1,
+                        Constants.MAX_GROUP_LIST_SIZE,
+                        new SortParam<>(Constants.NAME_FIELD_NAME, true),
+                        null).stream().map(GroupTO::getName).collect(Collectors.toList());
             }
         };
 
@@ -102,8 +102,8 @@ public class AnyObjectSearchPanel extends AbstractSearchPanel {
             @Override
             protected Map<String, PlainSchemaTO> load() {
                 return SchemaRestClient.<PlainSchemaTO>getSchemas(
-                    SchemaType.PLAIN, null, AnyTypeRestClient.read(type).getClasses().toArray(new String[]{})).
-                    stream().collect(Collectors.toMap(SchemaTO::getKey, Function.identity()));
+                        SchemaType.PLAIN, null, AnyTypeRestClient.read(type).getClasses().toArray(String[]::new)).
+                        stream().collect(Collectors.toMap(SchemaTO::getKey, Function.identity()));
             }
         };
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java
index 80ae8e439e..b912f238b7 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java
@@ -89,8 +89,8 @@ public class GroupSearchPanel extends AbstractSearchPanel {
             @Override
             protected Map<String, PlainSchemaTO> load() {
                 return SchemaRestClient.<PlainSchemaTO>getSchemas(
-                    SchemaType.PLAIN, null, AnyTypeRestClient.read(type).getClasses().toArray(new String[]{})).
-                    stream().collect(Collectors.toMap(SchemaTO::getKey, Function.identity()));
+                        SchemaType.PLAIN, null, AnyTypeRestClient.read(type).getClasses().toArray(String[]::new)).
+                        stream().collect(Collectors.toMap(SchemaTO::getKey, Function.identity()));
             }
         };
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/SchemaRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/SchemaRestClient.java
index fbf96815d7..adfe707cfa 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/SchemaRestClient.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/SchemaRestClient.java
@@ -62,7 +62,7 @@ public class SchemaRestClient extends BaseRestClient {
                         && anyTypeTO.getKind() != AnyTypeKind.GROUP)).
                         forEach((anyTypeTO) -> classes.addAll(anyTypeTO.getClasses()));
         }
-        return getSchemas(schemaType, null, classes.toArray(new String[] {}));
+        return getSchemas(schemaType, null, classes.toArray(String[]::new));
     }
 
     public static <T extends SchemaTO> List<T> getSchemas(
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
index fd6a0617da..64e551e525 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
@@ -131,7 +131,7 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
     protected void setSchemas(final List<String> anyTypeClasses, final Map<String, S> scs) {
         List<S> allSchemas = anyTypeClasses.isEmpty()
                 ? List.of()
-                : SchemaRestClient.getSchemas(getSchemaType(), null, anyTypeClasses.toArray(new String[] {}));
+                : SchemaRestClient.getSchemas(getSchemaType(), null, anyTypeClasses.toArray(String[]::new));
 
         scs.clear();
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
index 1813cc73f7..159d3a9296 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
@@ -117,7 +117,7 @@ public class Relationships extends WizardStep implements ICondition {
 
     private Fragment getViewFragment() {
         final Map<String, List<RelationshipTO>> relationships = new HashMap<>();
-        addRelationship(relationships, getCurrentRelationships().toArray(new RelationshipTO[] {}));
+        addRelationship(relationships, getCurrentRelationships().toArray(RelationshipTO[]::new));
 
         final Fragment viewFragment = new Fragment("relationships", "viewFragment", this);
         viewFragment.setOutputMarkupId(true);
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java
index 0acd3bde1c..f49f68f4d8 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java
@@ -171,7 +171,7 @@ public abstract class AbstractAttrs<S extends SchemaTO> extends Panel {
         if (anyTypeClasses.isEmpty()) {
             allSchemas = new ArrayList<>();
         } else {
-            allSchemas = SchemaRestClient.getSchemas(getSchemaType(), null, anyTypeClasses.toArray(new String[] {}));
+            allSchemas = SchemaRestClient.getSchemas(getSchemaType(), null, anyTypeClasses.toArray(String[]::new));
         }
 
         scs.clear();
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
index e897bf7dc7..6391f0a76a 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
@@ -62,7 +62,7 @@ public class SchemaRestClient extends BaseRestClient {
                         && anyTypeTO.getKind() != AnyTypeKind.GROUP)).
                         forEach((anyTypeTO) -> classes.addAll(anyTypeTO.getClasses()));
         }
-        return getSchemas(schemaType, null, classes.toArray(new String[] {}));
+        return getSchemas(schemaType, null, classes.toArray(String[]::new));
     }
 
     public static <T extends SchemaTO> List<T> getSchemas(
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPasswordRuleConf.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPasswordRuleConf.java
index b56d7ef992..16b501f24c 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPasswordRuleConf.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPasswordRuleConf.java
@@ -30,100 +30,25 @@ public class DefaultPasswordRuleConf extends AbstractPasswordRuleConf {
 
     private static final long serialVersionUID = -7988778083915548547L;
 
-    /**
-     * Minimum length.
-     */
     private int maxLength;
 
-    /**
-     * Maximum length.
-     */
     private int minLength;
 
-    /**
-     * Specify if one or more non alphanumeric characters are required.
-     */
-    private boolean nonAlphanumericRequired;
+    private int alphabetical;
 
-    /**
-     * Specify if one or more alphanumeric characters are required.
-     */
-    private boolean alphanumericRequired;
+    private int uppercase;
 
-    /**
-     * Specify if one or more digits are required.
-     */
-    private boolean digitRequired;
+    private int lowercase;
 
-    /**
-     * Specify if one or more lowercase alphabetic characters are required.
-     */
-    private boolean lowercaseRequired;
+    private int digit;
 
-    /**
-     * Specify if one or more uppercase alphabetic characters are required.
-     */
-    private boolean uppercaseRequired;
-
-    /**
-     * Specify if must start with a digit.
-     */
-    private boolean mustStartWithDigit;
+    private int special;
 
-    /**
-     * Specify if mustn't start with a digit.
-     */
-    private boolean mustntStartWithDigit;
+    private final List<Character> specialChars = new ArrayList<>();
 
-    /**
-     * Specify if must end with a digit.
-     */
-    private boolean mustEndWithDigit;
+    private final List<Character> illegalChars = new ArrayList<>();
 
-    /**
-     * Specify if mustn't end with a digit.
-     */
-    private boolean mustntEndWithDigit;
-
-    /**
-     * Specify if must start with a non alphanumeric character.
-     */
-    private boolean mustStartWithNonAlpha;
-
-    /**
-     * Specify if must start with a alphanumeric character.
-     */
-    private boolean mustStartWithAlpha;
-
-    /**
-     * Specify if mustn't start with a non alphanumeric character.
-     */
-    private boolean mustntStartWithNonAlpha;
-
-    /**
-     * Specify if mustn't start with a alphanumeric character.
-     */
-    private boolean mustntStartWithAlpha;
-
-    /**
-     * Specify if must end with a non alphanumeric character.
-     */
-    private boolean mustEndWithNonAlpha;
-
-    /**
-     * Specify if must end with a alphanumeric character.
-     */
-    private boolean mustEndWithAlpha;
-
-    /**
-     * Specify if mustn't end with a non alphanumeric character.
-     */
-    private boolean mustntEndWithNonAlpha;
-
-    /**
-     * Specify if mustn't end with a alphanumeric character.
-     */
-    private boolean mustntEndWithAlpha;
+    private int repeatSame;
 
     /**
      * Specify if using username as password is allowed.
@@ -142,32 +67,6 @@ public class DefaultPasswordRuleConf extends AbstractPasswordRuleConf {
             type = { SchemaType.PLAIN, SchemaType.DERIVED, SchemaType.VIRTUAL })
     private final List<String> schemasNotPermitted = new ArrayList<>();
 
-    /**
-     * Substrings not permitted as prefix.
-     */
-    private final List<String> prefixesNotPermitted = new ArrayList<>();
-
-    /**
-     * Substrings not permitted as suffix.
-     */
-    private final List<String> suffixesNotPermitted = new ArrayList<>();
-
-    public boolean isDigitRequired() {
-        return digitRequired;
-    }
-
-    public void setDigitRequired(final boolean digitRequired) {
-        this.digitRequired = digitRequired;
-    }
-
-    public boolean isLowercaseRequired() {
-        return lowercaseRequired;
-    }
-
-    public void setLowercaseRequired(final boolean lowercaseRequired) {
-        this.lowercaseRequired = lowercaseRequired;
-    }
-
     public int getMaxLength() {
         return maxLength;
     }
@@ -184,124 +83,64 @@ public class DefaultPasswordRuleConf extends AbstractPasswordRuleConf {
         this.minLength = minLength;
     }
 
-    public boolean isMustEndWithDigit() {
-        return mustEndWithDigit;
-    }
-
-    public void setMustEndWithDigit(final boolean mustEndWithDigit) {
-        this.mustEndWithDigit = mustEndWithDigit;
-    }
-
-    public boolean isMustEndWithNonAlpha() {
-        return mustEndWithNonAlpha;
-    }
-
-    public void setMustEndWithNonAlpha(final boolean mustEndWithNonAlpha) {
-        this.mustEndWithNonAlpha = mustEndWithNonAlpha;
-    }
-
-    public boolean isMustStartWithDigit() {
-        return mustStartWithDigit;
-    }
-
-    public void setMustStartWithDigit(final boolean mustStartWithDigit) {
-        this.mustStartWithDigit = mustStartWithDigit;
-    }
-
-    public boolean isMustStartWithNonAlpha() {
-        return mustStartWithNonAlpha;
+    public int getAlphabetical() {
+        return alphabetical;
     }
 
-    public void setMustStartWithNonAlpha(final boolean mustStartWithNonAlpha) {
-        this.mustStartWithNonAlpha = mustStartWithNonAlpha;
+    public void setAlphabetical(final int alphabetical) {
+        this.alphabetical = alphabetical;
     }
 
-    public boolean isMustntEndWithDigit() {
-        return mustntEndWithDigit;
+    public int getUppercase() {
+        return uppercase;
     }
 
-    public void setMustntEndWithDigit(final boolean mustntEndWithDigit) {
-        this.mustntEndWithDigit = mustntEndWithDigit;
+    public void setUppercase(final int uppercase) {
+        this.uppercase = uppercase;
     }
 
-    public boolean isMustntEndWithNonAlpha() {
-        return mustntEndWithNonAlpha;
+    public int getLowercase() {
+        return lowercase;
     }
 
-    public void setMustntEndWithNonAlpha(final boolean mustntEndWithNonAlpha) {
-        this.mustntEndWithNonAlpha = mustntEndWithNonAlpha;
+    public void setLowercase(final int lowercase) {
+        this.lowercase = lowercase;
     }
 
-    public boolean isMustntStartWithDigit() {
-        return mustntStartWithDigit;
+    public int getDigit() {
+        return digit;
     }
 
-    public void setMustntStartWithDigit(final boolean mustntStartWithDigit) {
-        this.mustntStartWithDigit = mustntStartWithDigit;
+    public void setDigit(final int digit) {
+        this.digit = digit;
     }
 
-    public boolean isMustntStartWithNonAlpha() {
-        return mustntStartWithNonAlpha;
+    public int getSpecial() {
+        return special;
     }
 
-    public void setMustntStartWithNonAlpha(final boolean mustntStartWithNonAlpha) {
-        this.mustntStartWithNonAlpha = mustntStartWithNonAlpha;
+    public void setSpecial(final int special) {
+        this.special = special;
     }
 
-    public boolean isNonAlphanumericRequired() {
-        return nonAlphanumericRequired;
+    @JacksonXmlElementWrapper(localName = "specialChars")
+    @JacksonXmlProperty(localName = "char")
+    public List<Character> getSpecialChars() {
+        return specialChars;
     }
 
-    public void setNonAlphanumericRequired(final boolean nonAlphanumericRequired) {
-        this.nonAlphanumericRequired = nonAlphanumericRequired;
+    @JacksonXmlElementWrapper(localName = "illegalChars")
+    @JacksonXmlProperty(localName = "char")
+    public List<Character> getIllegalChars() {
+        return illegalChars;
     }
 
-    public boolean isUppercaseRequired() {
-        return uppercaseRequired;
+    public int getRepeatSame() {
+        return repeatSame;
     }
 
-    public void setUppercaseRequired(final boolean uppercaseRequired) {
-        this.uppercaseRequired = uppercaseRequired;
-    }
-
-    public boolean isAlphanumericRequired() {
-        return alphanumericRequired;
-    }
-
-    public void setAlphanumericRequired(final boolean alphanumericRequired) {
-        this.alphanumericRequired = alphanumericRequired;
-    }
-
-    public boolean isMustEndWithAlpha() {
-        return mustEndWithAlpha;
-    }
-
-    public void setMustEndWithAlpha(final boolean mustEndWithAlpha) {
-        this.mustEndWithAlpha = mustEndWithAlpha;
-    }
-
-    public boolean isMustStartWithAlpha() {
-        return mustStartWithAlpha;
-    }
-
-    public void setMustStartWithAlpha(final boolean mustStartWithAlpha) {
-        this.mustStartWithAlpha = mustStartWithAlpha;
-    }
-
-    public boolean isMustntEndWithAlpha() {
-        return mustntEndWithAlpha;
-    }
-
-    public void setMustntEndWithAlpha(final boolean mustntEndWithAlpha) {
-        this.mustntEndWithAlpha = mustntEndWithAlpha;
-    }
-
-    public boolean isMustntStartWithAlpha() {
-        return mustntStartWithAlpha;
-    }
-
-    public void setMustntStartWithAlpha(final boolean mustntStartWithAlpha) {
-        this.mustntStartWithAlpha = mustntStartWithAlpha;
+    public void setRepeatSame(final int repeatSame) {
+        this.repeatSame = repeatSame;
     }
 
     public boolean isUsernameAllowed() {
@@ -318,21 +157,9 @@ public class DefaultPasswordRuleConf extends AbstractPasswordRuleConf {
         return wordsNotPermitted;
     }
 
-    @JacksonXmlElementWrapper(localName = "prefixesNotPermitted")
-    @JacksonXmlProperty(localName = "prefix")
-    public List<String> getPrefixesNotPermitted() {
-        return prefixesNotPermitted;
-    }
-
     @JacksonXmlElementWrapper(localName = "schemasNotPermitted")
     @JacksonXmlProperty(localName = "schema")
     public List<String> getSchemasNotPermitted() {
         return schemasNotPermitted;
     }
-
-    @JacksonXmlElementWrapper(localName = "suffixesNotPermitted")
-    @JacksonXmlProperty(localName = "suffix")
-    public List<String> getSuffixesNotPermitted() {
-        return suffixesNotPermitted;
-    }
 }
diff --git a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
index ef107f7820..34fa534d45 100644
--- a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
+++ b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
@@ -251,7 +251,7 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
         status.setOnSyncope(getOnSyncope(any, connObjectKeyItem, provision));
 
         List<ConnectorObject> connObjs = outboundMatcher.match(connectorManager.getConnector(
-                provision.getResource()), any, provision, Optional.of(moreAttrsToGet.toArray(new String[] {})));
+                provision.getResource()), any, provision, Optional.of(moreAttrsToGet.toArray(String[]::new)));
         if (!connObjs.isEmpty()) {
             status.setOnResource(ConnObjectUtils.getConnObjectTO(
                     outboundMatcher.getFIQL(connObjs.get(0), provision), connObjs.get(0).getAttributes()));
@@ -274,7 +274,7 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
         Stream<MappingItem> mapItems = Stream.concat(
                 provision.getMapping().getItems().stream(),
                 virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
-        OperationOptions options = MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(new String[0]));
+        OperationOptions options = MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(String[]::new));
 
         SyncDeltaBuilder syncDeltaBuilder = new SyncDeltaBuilder().
                 setToken(new SyncToken("")).
diff --git a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
index 6f7dcb09c0..ed35249621 100644
--- a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
+++ b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
@@ -417,7 +417,7 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
             provision = null;
             objectClass = resource.getOrgUnit().getObjectClass();
             options = MappingUtils.buildOperationOptions(
-                    resource.getOrgUnit().getItems().stream(), moreAttrsToGet.toArray(new String[0]));
+                    resource.getOrgUnit().getItems().stream(), moreAttrsToGet.toArray(String[]::new));
         } else {
             provision = getProvision(key, anyTypeKey);
             resource = provision.getResource();
@@ -426,7 +426,7 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
             Stream<MappingItem> mapItems = Stream.concat(
                     provision.getMapping().getItems().stream(),
                     virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
-            options = MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(new String[0]));
+            options = MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(String[]::new));
         }
 
         List<ConnObjectTO> connObjects = new ArrayList<>();
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/JavaDocUtils.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/JavaDocUtils.java
index 2a76632dc8..43b14f4977 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/JavaDocUtils.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/JavaDocUtils.java
@@ -43,7 +43,7 @@ public final class JavaDocUtils {
                 }
             }
             if (!javaDocURLs.isEmpty()) {
-                result = javaDocURLs.toArray(new URL[javaDocURLs.size()]);
+                result = javaDocURLs.toArray(URL[]::new);
             }
         }
 
diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index 1fad8310e4..05b0b89db3 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -22,12 +22,12 @@ under the License.
   <PasswordPolicy id="ce93fcda-dc3a-4369-a7b0-a6108c261c85" name="a password policy"
                   historyLength="1" allowNullPassword="1"/>
   <Implementation id="DefaultPasswordRuleConf1" type="PASSWORD_RULE" engine="JAVA"
-                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":8,"nonAlphanumericRequired":false,"alphanumericRequired":false,"digitRequired":false,"lowercaseRequired":false,"uppercaseRequired":false,"mustStartWithDigit":false,"mustntStartWithDigit":false,"mustEndWithDigit":false,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustE [...]
+                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":8,"wordsNotPermitted":["notpermitted1","notpermitted2"]}'/>
   <PasswordPolicyRule policy_id="ce93fcda-dc3a-4369-a7b0-a6108c261c85" implementation_id="DefaultPasswordRuleConf1"/>
   <PasswordPolicy id="986d1236-3ac5-4a19-810c-5ab21d79cba1"
                   name="sample password policy" historyLength="0" allowNullPassword="1"/> 
   <Implementation id="DefaultPasswordRuleConf2" type="PASSWORD_RULE"  engine="JAVA"
-                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":false,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":false,"uppercaseRequired":false,"mustStartWithDigit":false,"mustntStartWithDigit":false,"mustEndWithDigit":false,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustE [...]
+                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"digit":1,"wordsNotPermitted":["notpermitted1","notpermitted2"]}'/>
   <PasswordPolicyRule policy_id="986d1236-3ac5-4a19-810c-5ab21d79cba1" implementation_id="DefaultPasswordRuleConf2"/>
   <AccountPolicy id="20ab5a8c-4b0c-432c-b957-f7fb9784d9f7" name="an account policy"
                  propagateSuspension="0" maxAuthenticationAttempts="0"/>
@@ -41,11 +41,11 @@ under the License.
   <AccountPolicyRule policy_id="06e2ed52-6966-44aa-a177-a0ca7434201f" implementation_id="DefaultAccountRuleConf2"/>
   <PasswordPolicy id="55e5de0b-c79c-4e66-adda-251b6fb8579a" name="sample password policy" historyLength="0" allowNullPassword="0"/> 
   <Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
-                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
+                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"special":1,"specialChars":["@","!"],"digit":1,"lowercase":1,"uppercase":1,"wordsNotPermitted":["notpermitted1","notpermitted2"]}'/>
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
   <PropagationPolicy id="89d322db-9878-420c-b49c-67be13df9a12" name="sample propagation policy"
                      maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
-  
+
   <!-- Authentication policies -->
   <AuthPolicy id="659b9906-4b6e-4bc0-aca0-6809dff346d4" name="MyDefaultAuthPolicyConf"
               jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf","authModules":["LdapAuthenticationTest"]}'/>
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/PersistenceTestContext.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/PersistenceTestContext.java
index c7048c7424..8d5de57869 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/PersistenceTestContext.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/PersistenceTestContext.java
@@ -53,7 +53,7 @@ public class PersistenceTestContext {
         for (String location : System.getProperty("CORE_PROPERTIES").split(",")) {
             locations.add(resourceLoader.getResource(location));
         }
-        ppc.setLocations(locations.toArray(new Resource[0]));
+        ppc.setLocations(locations.toArray(Resource[]::new));
 
         return ppc;
     }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
index a7f568430b..b69f426f36 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
@@ -40,7 +40,6 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.persistence.jpa.AbstractTest;
-import org.apache.syncope.core.spring.policy.InvalidPasswordRuleConf;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.core.spring.security.PasswordGenerator;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
@@ -296,12 +295,7 @@ public class UserTest extends AbstractTest {
 
     @Test
     public void testPasswordGenerator() {
-        String password = "";
-        try {
-            password = passwordGenerator.generate(resourceDAO.find("ws-target-resource-nopropagation"));
-        } catch (InvalidPasswordRuleConf e) {
-            fail(e::getMessage);
-        }
+        String password = passwordGenerator.generate(resourceDAO.find("ws-target-resource-nopropagation"));
         assertNotNull(password);
 
         User user = userDAO.find("c9b2dec2-00a7-4855-97c0-d854842b4b24");
@@ -312,12 +306,7 @@ public class UserTest extends AbstractTest {
     @Test
     public void passwordGeneratorFailing() {
         assertThrows(IllegalArgumentException.class, () -> {
-            String password = "";
-            try {
-                password = passwordGenerator.generate(resourceDAO.find("ws-target-resource-nopropagation"));
-            } catch (InvalidPasswordRuleConf e) {
-                fail(e.getMessage());
-            }
+            String password = passwordGenerator.generate(resourceDAO.find("ws-target-resource-nopropagation"));
             assertNotNull(password);
 
             User user = userDAO.find("c9b2dec2-00a7-4855-97c0-d854842b4b24");
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index 05caca9153..d11af422b7 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -22,12 +22,12 @@ under the License.
   <PasswordPolicy id="ce93fcda-dc3a-4369-a7b0-a6108c261c85" name="a password policy"
                   historyLength="1" allowNullPassword="1"/>
   <Implementation id="DefaultPasswordRuleConf1" type="PASSWORD_RULE" engine="JAVA"
-                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":8,"nonAlphanumericRequired":false,"alphanumericRequired":false,"digitRequired":false,"lowercaseRequired":false,"uppercaseRequired":false,"mustStartWithDigit":false,"mustntStartWithDigit":false,"mustEndWithDigit":false,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustE [...]
+                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":8,"wordsNotPermitted":["notpermitted1","notpermitted2"]}'/>
   <PasswordPolicyRule policy_id="ce93fcda-dc3a-4369-a7b0-a6108c261c85" implementation_id="DefaultPasswordRuleConf1"/>
   <PasswordPolicy id="986d1236-3ac5-4a19-810c-5ab21d79cba1"
                   name="sample password policy" historyLength="0" allowNullPassword="1"/> 
   <Implementation id="DefaultPasswordRuleConf2" type="PASSWORD_RULE"  engine="JAVA"
-                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":false,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":false,"uppercaseRequired":false,"mustStartWithDigit":false,"mustntStartWithDigit":false,"mustEndWithDigit":false,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustE [...]
+                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"digit":1,"wordsNotPermitted":["notpermitted1","notpermitted2"]}'/>
   <PasswordPolicyRule policy_id="986d1236-3ac5-4a19-810c-5ab21d79cba1" implementation_id="DefaultPasswordRuleConf2"/>
   <AccountPolicy id="20ab5a8c-4b0c-432c-b957-f7fb9784d9f7" name="an account policy"
                  propagateSuspension="0" maxAuthenticationAttempts="0"/>
@@ -41,7 +41,7 @@ under the License.
   <AccountPolicyRule policy_id="06e2ed52-6966-44aa-a177-a0ca7434201f" implementation_id="DefaultAccountRuleConf2"/>
   <PasswordPolicy id="55e5de0b-c79c-4e66-adda-251b6fb8579a" name="sample password policy" historyLength="0" allowNullPassword="0"/> 
   <Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
-                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
+                  body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"special":1,"specialChars":["@","!"],"digit":1,"lowercase":1,"uppercase":1,"wordsNotPermitted":["notpermitted1","notpermitted2"]}'/>
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
   <PropagationPolicy id="89d322db-9878-420c-b49c-67be13df9a12" name="sample propagation policy"
                      maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
index 8a1f7d16ba..d6ba13da05 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
@@ -483,7 +483,7 @@ public class ConnectorFacadeProxy implements Connector {
             } else if (File.class.equals(propertySchemaClass)) {
                 value = new File(values.get(0).toString());
             } else if (String[].class.equals(propertySchemaClass)) {
-                value = values.toArray(new String[] {});
+                value = values.toArray(String[]::new);
             } else {
                 value = values.get(0) == null ? null : values.get(0).toString();
             }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultConnIdBundleManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultConnIdBundleManager.java
index 3c6d875918..9e18cfdb9c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultConnIdBundleManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultConnIdBundleManager.java
@@ -106,8 +106,8 @@ public class DefaultConnIdBundleManager implements ConnIdBundleManager {
                 + "\n\tFiles: {}", bundleFileURLs);
 
         // 2. Get connector info manager
-        ConnectorInfoManager manager = ConnectorInfoManagerFactory.getInstance().getLocalManager(
-                bundleFileURLs.toArray(new URL[bundleFileURLs.size()]));
+        ConnectorInfoManager manager =
+                ConnectorInfoManagerFactory.getInstance().getLocalManager(bundleFileURLs.toArray(URL[]::new));
         if (manager == null) {
             throw new NotFoundException("Local ConnectorInfoManager");
         }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
index a4c90bd90f..30d509256d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
@@ -90,7 +90,6 @@ import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
 import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheKey;
 import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
-import org.apache.syncope.core.spring.policy.InvalidPasswordRuleConf;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.core.spring.security.PasswordGenerator;
 import org.identityconnectors.framework.common.FrameworkUtil;
@@ -517,11 +516,7 @@ public class DefaultMappingManager implements MappingManager {
         }
 
         if (passwordAttrValue == null && provision.getResource().isRandomPwdIfNotProvided()) {
-            try {
-                passwordAttrValue = passwordGenerator.generate(provision.getResource());
-            } catch (InvalidPasswordRuleConf e) {
-                LOG.error("Could not generate policy-compliant random password for {}", account, e);
-            }
+            passwordAttrValue = passwordGenerator.generate(provision.getResource());
         }
 
         return passwordAttrValue;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index e6daff8a3d..31e95b8adf 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -749,7 +749,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                         orgUnit.isIgnoreCaseMatch(),
                         MappingUtils.buildOperationOptions(
                                 MappingUtils.getPropagationItems(orgUnit.getItems().stream()),
-                                moreAttrsToGet.toArray(new String[0])));
+                                moreAttrsToGet.toArray(String[]::new)));
             } catch (TimeoutException toe) {
                 LOG.debug("Request timeout", toe);
                 throw toe;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
index 046ab729b0..2f9445f0ef 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
@@ -260,7 +260,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         Set<String> moreAttrsToGet = new HashSet<>();
         profile.getActions().forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, provision)));
         List<ConnectorObject> connObjs = outboundMatcher.match(
-                profile.getConnector(), any, provision, Optional.of(moreAttrsToGet.toArray(new String[0])));
+                profile.getConnector(), any, provision, Optional.of(moreAttrsToGet.toArray(String[]::new)));
         LOG.debug("Match(es) found for {} as {}: {}", any, provision.getObjectClass(), connObjs);
 
         if (connObjs.size() > 1) {
@@ -454,7 +454,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
                 if (notificationsAvailable || auditRequested) {
                     resultStatus = AuditElements.Result.SUCCESS;
                     output = outboundMatcher.match(
-                            profile.getConnector(), any, provision, Optional.of(moreAttrsToGet.toArray(new String[0])));
+                            profile.getConnector(), any, provision, Optional.of(moreAttrsToGet.toArray(String[]::new)));
                 }
             } catch (IgnoreProvisionException e) {
                 throw e;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
index 33d2d6493c..da3b84aae8 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
@@ -131,7 +131,7 @@ public class OutboundMatcher {
                         connector,
                         rule.get().getFilter(any, provision),
                         provision,
-                        Optional.of(moreAttrsToGet.toArray(new String[0])),
+                        Optional.of(moreAttrsToGet.toArray(String[]::new)),
                         Optional.empty()));
             } else {
                 MappingUtils.getConnObjectKeyItem(provision).flatMap(connObjectKeyItem -> matchByConnObjectKeyValue(
@@ -139,7 +139,7 @@ public class OutboundMatcher {
                         connObjectKeyItem,
                         connObjectKeyValue,
                         provision,
-                        Optional.of(moreAttrsToGet.toArray(new String[0])),
+                        Optional.of(moreAttrsToGet.toArray(String[]::new)),
                         Optional.empty())).ifPresent(result::add);
             }
         } catch (RuntimeException e) {
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
index 62ceee8aa4..b67aa53f0f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
@@ -240,7 +240,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
             Set<String> moreAttrsToGet = new HashSet<>();
             actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, orgUnit)));
             OperationOptions options = MappingUtils.buildOperationOptions(
-                    MappingUtils.getPullItems(orgUnit.getItems().stream()), moreAttrsToGet.toArray(new String[0]));
+                    MappingUtils.getPullItems(orgUnit.getItems().stream()), moreAttrsToGet.toArray(String[]::new));
 
             RealmPullResultHandler handler = buildRealmHandler();
             handler.setProfile(profile);
@@ -326,7 +326,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                         MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
                         virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
                 OperationOptions options = MappingUtils.buildOperationOptions(
-                        mapItems, moreAttrsToGet.toArray(new String[0]));
+                        mapItems, moreAttrsToGet.toArray(String[]::new));
 
                 switch (pullTask.getPullMode()) {
                     case INCREMENTAL:
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
index 0b89fad338..12086e606f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
@@ -157,7 +157,7 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
                     provision.getObjectClass(),
                     reconFilterBuilder,
                     handler,
-                    MappingUtils.buildOperationOptions(mapItems, matg.toArray(new String[0])));
+                    MappingUtils.buildOperationOptions(mapItems, matg.toArray(String[]::new)));
 
             try {
                 setGroupOwners(ghandler);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
index 1afe25440c..67004b1b88 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
@@ -240,7 +240,7 @@ public class StreamPullJobDelegate extends PullJobDelegate implements SyncopeStr
             connector.fullReconciliation(
                     provision.getObjectClass(),
                     handler,
-                    MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(new String[0])));
+                    MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(String[]::new)));
 
             try {
                 setGroupOwners(ghandler);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
index bbf2614eae..0582f89470 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
@@ -50,10 +50,8 @@ import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
 import org.apache.syncope.core.provisioning.api.MappingManager;
-import org.apache.syncope.core.spring.policy.InvalidPasswordRuleConf;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.core.spring.security.PasswordGenerator;
-import org.apache.syncope.core.spring.security.SecureRandomUtils;
 import org.identityconnectors.common.security.GuardedByteArray;
 import org.identityconnectors.common.security.GuardedString;
 import org.identityconnectors.common.security.SecurityUtil;
@@ -201,15 +199,7 @@ public class ConnObjectUtils {
                     filter(resource -> resource != null && resource.getPasswordPolicy() != null).
                     forEach(resource -> passwordPolicies.add(resource.getPasswordPolicy()));
 
-            String password;
-            try {
-                password = passwordGenerator.generate(passwordPolicies);
-            } catch (InvalidPasswordRuleConf e) {
-                LOG.error("Could not generate policy-compliant random password for {}", userCR, e);
-
-                password = SecureRandomUtils.generateRandomPassword(16);
-            }
-            userCR.setPassword(password);
+            userCR.setPassword(passwordGenerator.generate(passwordPolicies));
         }
 
         return anyCR;
diff --git a/core/spring/pom.xml b/core/spring/pom.xml
index 56c7b367ea..f7a230b578 100644
--- a/core/spring/pom.xml
+++ b/core/spring/pom.xml
@@ -59,6 +59,11 @@ under the License.
       <artifactId>nimbus-jose-jwt</artifactId>
     </dependency>
 
+    <dependency>
+      <groupId>org.passay</groupId>
+      <artifactId>passay</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
index 1bc5af62ba..b8c2de24fe 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
@@ -18,11 +18,16 @@
  */
 package org.apache.syncope.core.spring.policy;
 
+import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Optional;
+import java.util.Properties;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
 import org.apache.syncope.common.lib.policy.PasswordRuleConf;
@@ -31,6 +36,18 @@ import org.apache.syncope.core.persistence.api.dao.PasswordRuleConfClass;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.spring.security.Encryptor;
+import org.passay.CharacterData;
+import org.passay.CharacterRule;
+import org.passay.EnglishCharacterData;
+import org.passay.IllegalCharacterRule;
+import org.passay.LengthRule;
+import org.passay.PasswordData;
+import org.passay.PasswordValidator;
+import org.passay.PropertiesMessageResolver;
+import org.passay.RepeatCharactersRule;
+import org.passay.Rule;
+import org.passay.RuleResult;
+import org.passay.UsernameRule;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.transaction.annotation.Transactional;
@@ -39,140 +56,110 @@ import org.springframework.util.CollectionUtils;
 @PasswordRuleConfClass(DefaultPasswordRuleConf.class)
 public class DefaultPasswordRule implements PasswordRule {
 
-    private static final Logger LOG = LoggerFactory.getLogger(DefaultPasswordRule.class);
+    protected static final Logger LOG = LoggerFactory.getLogger(DefaultPasswordRule.class);
 
-    private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
+    protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
 
-    private DefaultPasswordRuleConf conf;
+    public static List<Rule> conf2Rules(final DefaultPasswordRuleConf conf) {
+        List<Rule> rules = new ArrayList<>();
 
-    @Override
-    public PasswordRuleConf getConf() {
-        return conf;
-    }
-
-    @Override
-    public void setConf(final PasswordRuleConf conf) {
-        if (conf instanceof DefaultPasswordRuleConf) {
-            this.conf = (DefaultPasswordRuleConf) conf;
-        } else {
-            throw new IllegalArgumentException(
-                    DefaultPasswordRuleConf.class.getName() + " expected, got " + conf.getClass().getName());
-        }
-    }
-
-    protected void enforce(final String clear, final String username, final Set<String> wordsNotPermitted) {
-        // check length
-        if (conf.getMinLength() > 0 && conf.getMinLength() > clear.length()) {
-            throw new PasswordPolicyException("Password too short");
+        LengthRule lengthRule = new LengthRule();
+        if (conf.getMinLength() > 0) {
+            lengthRule.setMinimumLength(conf.getMinLength());
         }
-
-        if (conf.getMaxLength() > 0 && conf.getMaxLength() < clear.length()) {
-            throw new PasswordPolicyException("Password too long");
+        if (conf.getMaxLength() > 0) {
+            lengthRule.setMaximumLength(conf.getMaxLength());
         }
+        rules.add(lengthRule);
 
-        // check words not permitted
-        if (!conf.isUsernameAllowed() && username != null && username.equals(clear)) {
-            throw new PasswordPolicyException("Password mustn't be equal to username");
+        if (conf.getAlphabetical() > 0) {
+            rules.add(new CharacterRule(EnglishCharacterData.Alphabetical, conf.getAlphabetical()));
         }
 
-        wordsNotPermitted.stream().
-                filter(word -> StringUtils.containsIgnoreCase(clear, word)).
-                forEach(item -> {
-                    throw new PasswordPolicyException("Used word(s) not permitted");
-                });
-
-        // check digits occurrence
-        if (conf.isDigitRequired() && !PolicyPattern.DIGIT.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must contain digit(s)");
+        if (conf.getUppercase() > 0) {
+            rules.add(new CharacterRule(EnglishCharacterData.UpperCase, conf.getUppercase()));
         }
 
-        // check lowercase alphabetic characters occurrence
-        if (conf.isLowercaseRequired() && !PolicyPattern.ALPHA_LOWERCASE.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must contain lowercase alphabetic character(s)");
+        if (conf.getLowercase() > 0) {
+            rules.add(new CharacterRule(EnglishCharacterData.LowerCase, conf.getLowercase()));
         }
 
-        // check uppercase alphabetic characters occurrence
-        if (conf.isUppercaseRequired() && !PolicyPattern.ALPHA_UPPERCASE.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must contain uppercase alphabetic character(s)");
+        if (conf.getDigit() > 0) {
+            rules.add(new CharacterRule(EnglishCharacterData.Digit, conf.getDigit()));
         }
 
-        // check prefix
-        conf.getPrefixesNotPermitted().stream().
-                filter(clear::startsWith).
-                forEach(item -> {
-                    throw new PasswordPolicyException("Prefix not permitted");
-                });
-
-        // check suffix
-        conf.getSuffixesNotPermitted().stream().
-                filter(clear::endsWith).
-                forEach(item -> {
-                    throw new PasswordPolicyException("Suffix not permitted");
-                });
+        if (conf.getSpecial() > 0) {
+            rules.add(new CharacterRule(new CharacterData() {
 
-        // check digit first occurrence
-        if (conf.isMustStartWithDigit() && !PolicyPattern.FIRST_DIGIT.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must start with a digit");
-        }
-
-        if (conf.isMustntStartWithDigit() && PolicyPattern.FIRST_DIGIT.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password mustn't start with a digit");
-        }
+                @Override
+                public String getErrorCode() {
+                    return "INSUFFICIENT_SPECIAL";
+                }
 
-        // check digit last occurrence
-        if (conf.isMustEndWithDigit() && !PolicyPattern.LAST_DIGIT.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must end with a digit");
+                @Override
+                public String getCharacters() {
+                    return new String(ArrayUtils.toPrimitive(conf.getSpecialChars().toArray(Character[]::new)));
+                }
+            }, conf.getSpecial()));
         }
 
-        if (conf.isMustntEndWithDigit() && PolicyPattern.LAST_DIGIT.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password mustn't end with a digit");
+        if (!conf.getIllegalChars().isEmpty()) {
+            rules.add(new IllegalCharacterRule(
+                    ArrayUtils.toPrimitive(conf.getIllegalChars().toArray(Character[]::new))));
         }
 
-        // check alphanumeric characters occurence
-        if (conf.isAlphanumericRequired() && !PolicyPattern.ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must contain alphanumeric character(s)");
+        if (conf.getRepeatSame() > 0) {
+            rules.add(new RepeatCharactersRule(conf.getRepeatSame()));
         }
 
-        // check non alphanumeric characters occurence
-        if (conf.isNonAlphanumericRequired() && !PolicyPattern.NON_ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must contain non-alphanumeric character(s)");
+        if (!conf.isUsernameAllowed()) {
+            rules.add(new UsernameRule(true, true));
         }
 
-        // check alphanumeric character first occurrence
-        if (conf.isMustStartWithAlpha() && !PolicyPattern.FIRST_ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must start with an alphanumeric character");
-        }
+        return rules;
+    }
 
-        if (conf.isMustntStartWithAlpha() && PolicyPattern.FIRST_ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password mustn't start with an alphanumeric character");
-        }
+    protected DefaultPasswordRuleConf conf;
 
-        // check alphanumeric character last occurrence
-        if (conf.isMustEndWithAlpha() && !PolicyPattern.LAST_ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must end with an alphanumeric character");
-        }
+    protected PasswordValidator passwordValidator;
 
-        if (conf.isMustntEndWithAlpha() && PolicyPattern.LAST_ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password mustn't end with an alphanumeric character");
-        }
+    @Override
+    public PasswordRuleConf getConf() {
+        return conf;
+    }
 
-        // check non alphanumeric character first occurrence
-        if (conf.isMustStartWithNonAlpha() && !PolicyPattern.FIRST_NON_ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must start with a non-alphanumeric character");
-        }
+    @Override
+    public void setConf(final PasswordRuleConf conf) {
+        if (conf instanceof DefaultPasswordRuleConf) {
+            this.conf = (DefaultPasswordRuleConf) conf;
 
-        if (conf.isMustntStartWithNonAlpha() && PolicyPattern.FIRST_NON_ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password mustn't start with a non-alphanumeric character");
+            Properties passay = new Properties();
+            try (InputStream in = getClass().getResourceAsStream("/passay.properties")) {
+                passay.load(in);
+                passwordValidator = new PasswordValidator(new PropertiesMessageResolver(passay), conf2Rules(this.conf));
+            } catch (Exception e) {
+                throw new IllegalStateException("Could not initialize Passay", e);
+            }
+        } else {
+            throw new IllegalArgumentException(
+                    DefaultPasswordRuleConf.class.getName() + " expected, got " + conf.getClass().getName());
         }
+    }
 
-        // check non alphanumeric character last occurrence
-        if (conf.isMustEndWithNonAlpha() && !PolicyPattern.LAST_NON_ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password must end with a non-alphanumeric character");
+    protected void enforce(final String clear, final String username, final Set<String> wordsNotPermitted) {
+        RuleResult result = passwordValidator.validate(
+                username == null ? new PasswordData(clear) : new PasswordData(username, clear));
+        if (!result.isValid()) {
+            throw new PasswordPolicyException(passwordValidator.getMessages(result).
+                    stream().collect(Collectors.joining(",")));
         }
 
-        if (conf.isMustntEndWithNonAlpha() && PolicyPattern.LAST_NON_ALPHANUMERIC.matcher(clear).matches()) {
-            throw new PasswordPolicyException("Password mustn't end with a non-alphanumeric character");
-        }
+        // check words not permitted
+        wordsNotPermitted.stream().
+                filter(word -> StringUtils.containsIgnoreCase(clear, word)).findFirst().
+                ifPresent(word -> {
+                    throw new PasswordPolicyException("Used word(s) not permitted");
+                });
     }
 
     @Transactional(readOnly = true)
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/InvalidPasswordRuleConf.java b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/InvalidPasswordRuleConf.java
deleted file mode 100644
index e016a25ca2..0000000000
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/InvalidPasswordRuleConf.java
+++ /dev/null
@@ -1,37 +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.syncope.core.spring.policy;
-
-/**
- * Raise when the merge of two or more PasswordRuleconf instances led to an inconsistent condition.
- *
- * @see org.apache.syncope.common.lib.policy.PasswordRuleConf
- */
-public class InvalidPasswordRuleConf extends Exception {
-
-    private static final long serialVersionUID = 4810651743226663580L;
-
-    public InvalidPasswordRuleConf(final String msg) {
-        super(msg);
-    }
-
-    public InvalidPasswordRuleConf(final String msg, final Exception e) {
-        super(msg, e);
-    }
-}
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
index f3bf0142ce..b82e80c50b 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
@@ -20,13 +20,14 @@ package org.apache.syncope.core.spring.security;
 
 import java.util.ArrayList;
 import java.util.List;
-import org.apache.commons.lang3.StringUtils;
+import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.spring.ImplementationManager;
-import org.apache.syncope.core.spring.policy.InvalidPasswordRuleConf;
-import org.apache.syncope.core.spring.policy.PolicyPattern;
+import org.apache.syncope.core.spring.policy.DefaultPasswordRule;
+import org.passay.CharacterRule;
+import org.passay.EnglishCharacterData;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.transaction.annotation.Transactional;
@@ -49,7 +50,7 @@ public class DefaultPasswordGenerator implements PasswordGenerator {
 
     @Transactional(readOnly = true)
     @Override
-    public String generate(final ExternalResource resource) throws InvalidPasswordRuleConf {
+    public String generate(final ExternalResource resource) {
         List<PasswordPolicy> policies = new ArrayList<>();
 
         if (resource.getPasswordPolicy() != null) {
@@ -60,14 +61,14 @@ public class DefaultPasswordGenerator implements PasswordGenerator {
     }
 
     @Override
-    public String generate(final List<PasswordPolicy> policies) throws InvalidPasswordRuleConf {
-        List<DefaultPasswordRuleConf> defaultRuleConfs = new ArrayList<>();
+    public String generate(final List<PasswordPolicy> policies) {
+        List<DefaultPasswordRuleConf> ruleConfs = new ArrayList<>();
 
         policies.stream().forEach(policy -> policy.getRules().forEach(impl -> {
             try {
                 ImplementationManager.buildPasswordRule(impl).ifPresent(rule -> {
                     if (rule.getConf() instanceof DefaultPasswordRuleConf) {
-                        defaultRuleConfs.add((DefaultPasswordRuleConf) rule.getConf());
+                        ruleConfs.add((DefaultPasswordRuleConf) rule.getConf());
                     }
                 });
             } catch (Exception e) {
@@ -75,12 +76,10 @@ public class DefaultPasswordGenerator implements PasswordGenerator {
             }
         }));
 
-        DefaultPasswordRuleConf ruleConf = merge(defaultRuleConfs);
-        check(ruleConf);
-        return generate(ruleConf);
+        return generate(merge(ruleConfs));
     }
 
-    protected static DefaultPasswordRuleConf merge(final List<DefaultPasswordRuleConf> defaultRuleConfs) {
+    protected DefaultPasswordRuleConf merge(final List<DefaultPasswordRuleConf> defaultRuleConfs) {
         DefaultPasswordRuleConf result = new DefaultPasswordRuleConf();
         result.setMinLength(VERY_MIN_LENGTH);
         result.setMaxLength(VERY_MAX_LENGTH);
@@ -90,240 +89,71 @@ public class DefaultPasswordGenerator implements PasswordGenerator {
                 result.setMinLength(ruleConf.getMinLength());
             }
 
-            if ((ruleConf.getMaxLength() != 0) && ((ruleConf.getMaxLength() < result.getMaxLength()))) {
+            if (ruleConf.getMaxLength() > 0 && ruleConf.getMaxLength() < result.getMaxLength()) {
                 result.setMaxLength(ruleConf.getMaxLength());
             }
-            result.getPrefixesNotPermitted().addAll(ruleConf.getPrefixesNotPermitted());
-            result.getSuffixesNotPermitted().addAll(ruleConf.getSuffixesNotPermitted());
 
-            if (!result.isNonAlphanumericRequired()) {
-                result.setNonAlphanumericRequired(ruleConf.isNonAlphanumericRequired());
+            if (ruleConf.getUppercase() > result.getUppercase()) {
+                result.setUppercase(ruleConf.getUppercase());
             }
 
-            if (!result.isAlphanumericRequired()) {
-                result.setAlphanumericRequired(ruleConf.isAlphanumericRequired());
-            }
-            if (!result.isDigitRequired()) {
-                result.setDigitRequired(ruleConf.isDigitRequired());
+            if (ruleConf.getLowercase() > result.getLowercase()) {
+                result.setLowercase(ruleConf.getLowercase());
             }
 
-            if (!result.isLowercaseRequired()) {
-                result.setLowercaseRequired(ruleConf.isLowercaseRequired());
-            }
-            if (!result.isUppercaseRequired()) {
-                result.setUppercaseRequired(ruleConf.isUppercaseRequired());
-            }
-            if (!result.isMustStartWithDigit()) {
-                result.setMustStartWithDigit(ruleConf.isMustStartWithDigit());
-            }
-            if (!result.isMustntStartWithDigit()) {
-                result.setMustntStartWithDigit(ruleConf.isMustntStartWithDigit());
-            }
-            if (!result.isMustEndWithDigit()) {
-                result.setMustEndWithDigit(ruleConf.isMustEndWithDigit());
-            }
-            if (result.isMustntEndWithDigit()) {
-                result.setMustntEndWithDigit(ruleConf.isMustntEndWithDigit());
+            if (ruleConf.getDigit() > result.getDigit()) {
+                result.setDigit(ruleConf.getDigit());
             }
-            if (!result.isMustStartWithAlpha()) {
-                result.setMustStartWithAlpha(ruleConf.isMustStartWithAlpha());
-            }
-            if (!result.isMustntStartWithAlpha()) {
-                result.setMustntStartWithAlpha(ruleConf.isMustntStartWithAlpha());
-            }
-            if (!result.isMustStartWithNonAlpha()) {
-                result.setMustStartWithNonAlpha(ruleConf.isMustStartWithNonAlpha());
-            }
-            if (!result.isMustntStartWithNonAlpha()) {
-                result.setMustntStartWithNonAlpha(ruleConf.isMustntStartWithNonAlpha());
-            }
-            if (!result.isMustEndWithNonAlpha()) {
-                result.setMustEndWithNonAlpha(ruleConf.isMustEndWithNonAlpha());
+
+            if (ruleConf.getSpecial() > result.getSpecial()) {
+                result.setSpecial(ruleConf.getSpecial());
             }
-            if (!result.isMustntEndWithNonAlpha()) {
-                result.setMustntEndWithNonAlpha(ruleConf.isMustntEndWithNonAlpha());
+
+            if (!ruleConf.getSpecialChars().isEmpty()) {
+                result.getSpecialChars().addAll(ruleConf.getSpecialChars().stream().
+                        filter(c -> !result.getSpecialChars().contains(c)).collect(Collectors.toList()));
             }
-            if (!result.isMustEndWithAlpha()) {
-                result.setMustEndWithAlpha(ruleConf.isMustEndWithAlpha());
+
+            if (!ruleConf.getIllegalChars().isEmpty()) {
+                result.getIllegalChars().addAll(ruleConf.getIllegalChars().stream().
+                        filter(c -> !result.getIllegalChars().contains(c)).collect(Collectors.toList()));
             }
-            if (!result.isMustntEndWithAlpha()) {
-                result.setMustntEndWithAlpha(ruleConf.isMustntEndWithAlpha());
+
+            if (ruleConf.getRepeatSame() > result.getRepeatSame()) {
+                result.setRepeatSame(ruleConf.getRepeatSame());
             }
+
             if (!result.isUsernameAllowed()) {
                 result.setUsernameAllowed(ruleConf.isUsernameAllowed());
             }
+
+            if (!ruleConf.getWordsNotPermitted().isEmpty()) {
+                result.getWordsNotPermitted().addAll(ruleConf.getWordsNotPermitted().stream().
+                        filter(w -> !result.getWordsNotPermitted().contains(w)).collect(Collectors.toList()));
+            }
         });
 
         if (result.getMinLength() == 0) {
             result.setMinLength(
                     result.getMaxLength() < MIN_LENGTH_IF_ZERO ? result.getMaxLength() : MIN_LENGTH_IF_ZERO);
         }
-
-        return result;
-    }
-
-    protected static void check(final DefaultPasswordRuleConf defaultPasswordRuleConf)
-            throws InvalidPasswordRuleConf {
-
-        if (defaultPasswordRuleConf.isMustEndWithAlpha() && defaultPasswordRuleConf.isMustntEndWithAlpha()) {
-            throw new InvalidPasswordRuleConf(
-                    "mustEndWithAlpha and mustntEndWithAlpha are both true");
-        }
-        if (defaultPasswordRuleConf.isMustEndWithAlpha() && defaultPasswordRuleConf.isMustEndWithDigit()) {
-            throw new InvalidPasswordRuleConf(
-                    "mustEndWithAlpha and mustEndWithDigit are both true");
-        }
-        if (defaultPasswordRuleConf.isMustEndWithDigit() && defaultPasswordRuleConf.isMustntEndWithDigit()) {
-            throw new InvalidPasswordRuleConf(
-                    "mustEndWithDigit and mustntEndWithDigit are both true");
-        }
-        if (defaultPasswordRuleConf.isMustEndWithNonAlpha() && defaultPasswordRuleConf.isMustntEndWithNonAlpha()) {
-            throw new InvalidPasswordRuleConf(
-                    "mustEndWithNonAlpha and mustntEndWithNonAlpha are both true");
-        }
-        if (defaultPasswordRuleConf.isMustStartWithAlpha() && defaultPasswordRuleConf.isMustntStartWithAlpha()) {
-            throw new InvalidPasswordRuleConf(
-                    "mustStartWithAlpha and mustntStartWithAlpha are both true");
-        }
-        if (defaultPasswordRuleConf.isMustStartWithAlpha() && defaultPasswordRuleConf.isMustStartWithDigit()) {
-            throw new InvalidPasswordRuleConf(
-                    "mustStartWithAlpha and mustStartWithDigit are both true");
+        if (result.getMinLength() > result.getMaxLength()) {
+            result.setMaxLength(result.getMinLength());
         }
-        if (defaultPasswordRuleConf.isMustStartWithDigit() && defaultPasswordRuleConf.isMustntStartWithDigit()) {
-            throw new InvalidPasswordRuleConf(
-                    "mustStartWithDigit and mustntStartWithDigit are both true");
-        }
-        if (defaultPasswordRuleConf.isMustStartWithNonAlpha() && defaultPasswordRuleConf.isMustntStartWithNonAlpha()) {
-            throw new InvalidPasswordRuleConf(
-                    "mustStartWithNonAlpha and mustntStartWithNonAlpha are both true");
-        }
-        if (defaultPasswordRuleConf.getMinLength() > defaultPasswordRuleConf.getMaxLength()) {
-            throw new InvalidPasswordRuleConf(
-                    "Minimun length (" + defaultPasswordRuleConf.getMinLength() + ')'
-                    + "is greater than maximum length (" + defaultPasswordRuleConf.getMaxLength() + ')');
-        }
-    }
-
-    protected static String generate(final DefaultPasswordRuleConf ruleConf) {
-        String[] generatedPassword = new String[ruleConf.getMinLength()];
 
-        for (int i = 0; i < generatedPassword.length; i++) {
-            generatedPassword[i] = StringUtils.EMPTY;
-        }
-
-        checkStartChar(generatedPassword, ruleConf);
-
-        checkEndChar(generatedPassword, ruleConf);
-
-        checkRequired(generatedPassword, ruleConf);
-
-        for (int firstEmptyChar = firstEmptyChar(generatedPassword);
-                firstEmptyChar < generatedPassword.length - 1; firstEmptyChar++) {
-
-            generatedPassword[firstEmptyChar] = SecureRandomUtils.generateRandomLetter();
-        }
-
-        checkPrefixAndSuffix(generatedPassword, ruleConf);
-
-        return StringUtils.join(generatedPassword);
-    }
-
-    protected static void checkStartChar(final String[] generatedPassword, final DefaultPasswordRuleConf ruleConf) {
-        if (ruleConf.isMustStartWithAlpha()) {
-            generatedPassword[0] = SecureRandomUtils.generateRandomLetter();
-        }
-        if (ruleConf.isMustStartWithNonAlpha() || ruleConf.isMustStartWithDigit()) {
-            generatedPassword[0] = SecureRandomUtils.generateRandomNumber();
-        }
-        if (ruleConf.isMustntStartWithAlpha()) {
-            generatedPassword[0] = SecureRandomUtils.generateRandomNumber();
-        }
-        if (ruleConf.isMustntStartWithDigit()) {
-            generatedPassword[0] = SecureRandomUtils.generateRandomLetter();
-        }
-        if (ruleConf.isMustntStartWithNonAlpha()) {
-            generatedPassword[0] = SecureRandomUtils.generateRandomLetter();
-        }
-
-        if (StringUtils.EMPTY.equals(generatedPassword[0])) {
-            generatedPassword[0] = SecureRandomUtils.generateRandomLetter();
-        }
-    }
-
-    protected static void checkEndChar(final String[] generatedPassword, final DefaultPasswordRuleConf ruleConf) {
-        if (ruleConf.isMustEndWithAlpha()) {
-            generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomLetter();
-        }
-        if (ruleConf.isMustEndWithNonAlpha() || ruleConf.isMustEndWithDigit()) {
-            generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomNumber();
-        }
-
-        if (ruleConf.isMustntEndWithAlpha()) {
-            generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomNumber();
-        }
-        if (ruleConf.isMustntEndWithDigit()) {
-            generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomLetter();
-        }
-        if (ruleConf.isMustntEndWithNonAlpha()) {
-            generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomLetter();
-        }
-
-        if (StringUtils.EMPTY.equals(generatedPassword[ruleConf.getMinLength() - 1])) {
-            generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomLetter();
-        }
-    }
-
-    protected static int firstEmptyChar(final String[] generatedPStrings) {
-        int index = 0;
-        while (!generatedPStrings[index].isEmpty()) {
-            index++;
-        }
-        return index;
-    }
-
-    protected static void checkRequired(final String[] generatedPassword, final DefaultPasswordRuleConf ruleConf) {
-        if (ruleConf.isDigitRequired()
-                && !PolicyPattern.DIGIT.matcher(StringUtils.join(generatedPassword)).matches()) {
-
-            generatedPassword[firstEmptyChar(generatedPassword)] = SecureRandomUtils.generateRandomNumber();
-        }
-
-        if (ruleConf.isUppercaseRequired()
-                && !PolicyPattern.ALPHA_UPPERCASE.matcher(StringUtils.join(generatedPassword)).matches()) {
-
-            generatedPassword[firstEmptyChar(generatedPassword)] =
-                    SecureRandomUtils.generateRandomLetter().toUpperCase();
-        }
-
-        if (ruleConf.isLowercaseRequired()
-                && !PolicyPattern.ALPHA_LOWERCASE.matcher(StringUtils.join(generatedPassword)).matches()) {
-
-            generatedPassword[firstEmptyChar(generatedPassword)] =
-                    SecureRandomUtils.generateRandomLetter().toLowerCase();
-        }
-
-        if (ruleConf.isNonAlphanumericRequired()
-                && !PolicyPattern.NON_ALPHANUMERIC.matcher(StringUtils.join(generatedPassword)).matches()) {
-
-            generatedPassword[firstEmptyChar(generatedPassword)] =
-                    SecureRandomUtils.generateRandomNonAlphanumericChar(
-                            PolicyPattern.NON_ALPHANUMERIC_CHARS_FOR_PASSWORD_VALUES);
-        }
+        return result;
     }
 
-    protected static void checkPrefixAndSuffix(
-            final String[] generatedPassword, final DefaultPasswordRuleConf ruleConf) {
-
-        ruleConf.getPrefixesNotPermitted().forEach(prefix -> {
-            if (StringUtils.join(generatedPassword).startsWith(prefix)) {
-                checkStartChar(generatedPassword, ruleConf);
-            }
-        });
-
-        ruleConf.getSuffixesNotPermitted().forEach(suffix -> {
-            if (StringUtils.join(generatedPassword).endsWith(suffix)) {
-                checkEndChar(generatedPassword, ruleConf);
-            }
-        });
+    protected String generate(final DefaultPasswordRuleConf ruleConf) {
+        List<CharacterRule> characterRules = DefaultPasswordRule.conf2Rules(ruleConf).stream().
+                filter(CharacterRule.class::isInstance).map(CharacterRule.class::cast).
+                collect(Collectors.toList());
+        if (characterRules.isEmpty()) {
+            int halfMinLength = ruleConf.getMinLength() / 2;
+            characterRules = List.of(
+                    new CharacterRule(EnglishCharacterData.Alphabetical, halfMinLength),
+                    new CharacterRule(EnglishCharacterData.Digit, halfMinLength));
+        }
+        return SecureRandomUtils.passwordGenerator().generatePassword(ruleConf.getMinLength(), characterRules);
     }
 }
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/PasswordGenerator.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/PasswordGenerator.java
index a73a8f2092..17518b1ed2 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/PasswordGenerator.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/PasswordGenerator.java
@@ -21,12 +21,10 @@ package org.apache.syncope.core.spring.security;
 import java.util.List;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
-import org.apache.syncope.core.spring.policy.InvalidPasswordRuleConf;
 
 public interface PasswordGenerator {
 
-    String generate(ExternalResource resource) throws InvalidPasswordRuleConf;
-
-    String generate(List<PasswordPolicy> policies) throws InvalidPasswordRuleConf;
+    String generate(ExternalResource resource);
 
+    String generate(List<PasswordPolicy> policies);
 }
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
index 9d7be34991..d27e37240a 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
@@ -23,11 +23,14 @@ import com.fasterxml.uuid.impl.RandomBasedGenerator;
 import java.security.SecureRandom;
 import java.util.UUID;
 import org.apache.commons.text.RandomStringGenerator;
+import org.passay.PasswordGenerator;
 
 public final class SecureRandomUtils {
 
     private static final SecureRandom RANDOM = new SecureRandom();
 
+    private static final PasswordGenerator PASSWORD_GENERATOR = new PasswordGenerator(RANDOM);
+
     private static final RandomStringGenerator FOR_PASSWORD = new RandomStringGenerator.Builder().
             usingRandom(RANDOM::nextInt).
             withinRange('0', 'z').
@@ -79,6 +82,10 @@ public final class SecureRandomUtils {
         return UUID_GENERATOR.generate();
     }
 
+    public static PasswordGenerator passwordGenerator() {
+        return PASSWORD_GENERATOR;
+    }
+
     private SecureRandomUtils() {
         // private constructor for static utility class
     }
diff --git a/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java b/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java
index 4e32e2fb31..fa292318ac 100644
--- a/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java
+++ b/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java
@@ -37,27 +37,12 @@ import java.util.stream.Collectors;
 @SpringJUnitConfig(classes = { SpringTestConfiguration.class })
 public class ImplementationManagerTest {
 
-    private static DefaultPasswordRuleConf createBaseDefaultPasswordRuleConf() {
+    public static DefaultPasswordRuleConf createBaseDefaultPasswordRuleConf() {
         DefaultPasswordRuleConf baseDefaultPasswordRuleConf = new DefaultPasswordRuleConf();
-        baseDefaultPasswordRuleConf.setAlphanumericRequired(false);
-        baseDefaultPasswordRuleConf.setDigitRequired(false);
-        baseDefaultPasswordRuleConf.setLowercaseRequired(false);
+        baseDefaultPasswordRuleConf.setUppercase(1);
+        baseDefaultPasswordRuleConf.setDigit(1);
         baseDefaultPasswordRuleConf.setMaxLength(1000);
         baseDefaultPasswordRuleConf.setMinLength(8);
-        baseDefaultPasswordRuleConf.setMustEndWithAlpha(false);
-        baseDefaultPasswordRuleConf.setMustEndWithDigit(false);
-        baseDefaultPasswordRuleConf.setMustEndWithNonAlpha(false);
-        baseDefaultPasswordRuleConf.setMustStartWithAlpha(false);
-        baseDefaultPasswordRuleConf.setMustStartWithDigit(false);
-        baseDefaultPasswordRuleConf.setMustStartWithNonAlpha(false);
-        baseDefaultPasswordRuleConf.setMustntEndWithAlpha(false);
-        baseDefaultPasswordRuleConf.setMustntEndWithDigit(false);
-        baseDefaultPasswordRuleConf.setMustntEndWithNonAlpha(false);
-        baseDefaultPasswordRuleConf.setMustntStartWithAlpha(false);
-        baseDefaultPasswordRuleConf.setMustntStartWithDigit(false);
-        baseDefaultPasswordRuleConf.setMustntStartWithNonAlpha(false);
-        baseDefaultPasswordRuleConf.setNonAlphanumericRequired(false);
-        baseDefaultPasswordRuleConf.setUppercaseRequired(false);
         return baseDefaultPasswordRuleConf;
     }
 
diff --git a/core/spring/src/test/java/org/apache/syncope/core/spring/security/PasswordGeneratorTest.java b/core/spring/src/test/java/org/apache/syncope/core/spring/security/PasswordGeneratorTest.java
index 0cf68d9f4a..fd9696725d 100644
--- a/core/spring/src/test/java/org/apache/syncope/core/spring/security/PasswordGeneratorTest.java
+++ b/core/spring/src/test/java/org/apache/syncope/core/spring/security/PasswordGeneratorTest.java
@@ -20,18 +20,12 @@ package org.apache.syncope.core.spring.security;
 
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.fail;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
-import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.ImplementationManagerTest;
 import org.apache.syncope.core.spring.SpringTestConfiguration;
-import org.apache.syncope.core.spring.policy.InvalidPasswordRuleConf;
-import org.apache.syncope.core.spring.policy.PolicyPattern;
 import org.junit.jupiter.api.Test;
 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 
@@ -40,148 +34,80 @@ public class PasswordGeneratorTest {
 
     private final DefaultPasswordGenerator passwordGenerator = new DefaultPasswordGenerator();
 
-    private static DefaultPasswordRuleConf createBaseDefaultPasswordRuleConf() {
-        DefaultPasswordRuleConf baseDefaultPasswordRuleConf = new DefaultPasswordRuleConf();
-        baseDefaultPasswordRuleConf.setAlphanumericRequired(false);
-        baseDefaultPasswordRuleConf.setDigitRequired(false);
-        baseDefaultPasswordRuleConf.setLowercaseRequired(false);
-        baseDefaultPasswordRuleConf.setMaxLength(1000);
-        baseDefaultPasswordRuleConf.setMinLength(8);
-        baseDefaultPasswordRuleConf.setMustEndWithAlpha(false);
-        baseDefaultPasswordRuleConf.setMustEndWithDigit(false);
-        baseDefaultPasswordRuleConf.setMustEndWithNonAlpha(false);
-        baseDefaultPasswordRuleConf.setMustStartWithAlpha(false);
-        baseDefaultPasswordRuleConf.setMustStartWithDigit(false);
-        baseDefaultPasswordRuleConf.setMustStartWithNonAlpha(false);
-        baseDefaultPasswordRuleConf.setMustntEndWithAlpha(false);
-        baseDefaultPasswordRuleConf.setMustntEndWithDigit(false);
-        baseDefaultPasswordRuleConf.setMustntEndWithNonAlpha(false);
-        baseDefaultPasswordRuleConf.setMustntStartWithAlpha(false);
-        baseDefaultPasswordRuleConf.setMustntStartWithDigit(false);
-        baseDefaultPasswordRuleConf.setMustntStartWithNonAlpha(false);
-        baseDefaultPasswordRuleConf.setNonAlphanumericRequired(false);
-        baseDefaultPasswordRuleConf.setUppercaseRequired(false);
-        return baseDefaultPasswordRuleConf;
+    @Test
+    public void digit() {
+        DefaultPasswordRuleConf pwdRuleConf = ImplementationManagerTest.createBaseDefaultPasswordRuleConf();
+        pwdRuleConf.setDigit(1);
+        TestImplementation passwordRule = new TestImplementation();
+        passwordRule.setBody(POJOHelper.serialize(pwdRuleConf));
+
+        String generatedPassword = passwordGenerator.generate(List.of(new TestPasswordPolicy(passwordRule)));
+
+        assertTrue(generatedPassword.chars().anyMatch(Character::isDigit));
     }
 
     @Test
-    public void startEndWithDigit() throws InvalidPasswordRuleConf {
-        DefaultPasswordRuleConf pwdRuleConf1 = createBaseDefaultPasswordRuleConf();
-        pwdRuleConf1.setMustStartWithDigit(true);
-        TestImplementation passwordRule1 = new TestImplementation();
-        passwordRule1.setBody(POJOHelper.serialize(pwdRuleConf1));
-        TestPasswordPolicy policy1 = new TestPasswordPolicy();
-        policy1.add(passwordRule1);
-
-        DefaultPasswordRuleConf pwdRuleConf2 = createBaseDefaultPasswordRuleConf();
-        pwdRuleConf2.setMustEndWithDigit(true);
-        TestImplementation passwordRule2 = new TestImplementation();
-        passwordRule2.setBody(POJOHelper.serialize(pwdRuleConf2));
-        TestPasswordPolicy policy2 = new TestPasswordPolicy();
-        policy2.add(passwordRule2);
-
-        List<PasswordPolicy> policies = new ArrayList<>();
-        policies.add(policy1);
-        policies.add(policy2);
-        String generatedPassword = passwordGenerator.generate(policies);
-        assertTrue(Character.isDigit(generatedPassword.charAt(0)));
-        assertTrue(Character.isDigit(generatedPassword.charAt(generatedPassword.length() - 1)));
+    public void alphabetical() {
+        DefaultPasswordRuleConf pwdRuleConf = ImplementationManagerTest.createBaseDefaultPasswordRuleConf();
+        pwdRuleConf.setAlphabetical(1);
+        TestImplementation passwordRule = new TestImplementation();
+        passwordRule.setBody(POJOHelper.serialize(pwdRuleConf));
+
+        String generatedPassword = passwordGenerator.generate(List.of(new TestPasswordPolicy(passwordRule)));
+
+        assertTrue(generatedPassword.chars().anyMatch(Character::isAlphabetic));
     }
 
     @Test
-    public void startWithDigitAndWithAlpha() throws InvalidPasswordRuleConf {
-        DefaultPasswordRuleConf pwdRuleConf1 = createBaseDefaultPasswordRuleConf();
-        pwdRuleConf1.setMustStartWithDigit(true);
-        TestImplementation passwordRule1 = new TestImplementation();
-        passwordRule1.setBody(POJOHelper.serialize(pwdRuleConf1));
-        TestPasswordPolicy policy1 = new TestPasswordPolicy();
-        policy1.add(passwordRule1);
-
-        DefaultPasswordRuleConf pwdRuleConf2 = createBaseDefaultPasswordRuleConf();
-        pwdRuleConf2.setMustEndWithAlpha(true);
-        TestImplementation passwordRule2 = new TestImplementation();
-        passwordRule2.setBody(POJOHelper.serialize(pwdRuleConf2));
-        TestPasswordPolicy policy2 = new TestPasswordPolicy();
-        policy2.add(passwordRule2);
-
-        List<PasswordPolicy> policies = new ArrayList<>();
-        policies.add(policy1);
-        policies.add(policy2);
-        String generatedPassword = passwordGenerator.generate(policies);
-        assertTrue(Character.isDigit(generatedPassword.charAt(0)));
-        assertTrue(Character.isLetter(generatedPassword.charAt(generatedPassword.length() - 1)));
+    public void lowercase() {
+        DefaultPasswordRuleConf pwdRuleConf = ImplementationManagerTest.createBaseDefaultPasswordRuleConf();
+        pwdRuleConf.setLowercase(1);
+        TestImplementation passwordRule = new TestImplementation();
+        passwordRule.setBody(POJOHelper.serialize(pwdRuleConf));
+
+        String generatedPassword = passwordGenerator.generate(List.of(new TestPasswordPolicy(passwordRule)));
+
+        assertTrue(generatedPassword.chars().anyMatch(Character::isLowerCase));
     }
 
     @Test
-    public void passwordWithNonAlpha() throws InvalidPasswordRuleConf {
-        DefaultPasswordRuleConf pwdRuleConf1 = createBaseDefaultPasswordRuleConf();
-        pwdRuleConf1.setNonAlphanumericRequired(true);
-        TestImplementation passwordRule1 = new TestImplementation();
-        passwordRule1.setBody(POJOHelper.serialize(pwdRuleConf1));
-        TestPasswordPolicy policy1 = new TestPasswordPolicy();
-        policy1.add(passwordRule1);
-
-        DefaultPasswordRuleConf pwdRuleConf2 = createBaseDefaultPasswordRuleConf();
-        pwdRuleConf2.setMustEndWithAlpha(true);
-        TestImplementation passwordRule2 = new TestImplementation();
-        passwordRule2.setBody(POJOHelper.serialize(pwdRuleConf2));
-        TestPasswordPolicy policy2 = new TestPasswordPolicy();
-        policy2.add(passwordRule2);
-
-        List<PasswordPolicy> policies = new ArrayList<>();
-        policies.add(policy1);
-        policies.add(policy2);
-        String generatedPassword = passwordGenerator.generate(policies);
-        assertTrue(PolicyPattern.NON_ALPHANUMERIC.matcher(generatedPassword).matches());
-        assertTrue(Character.isLetter(generatedPassword.charAt(generatedPassword.length() - 1)));
+    public void uppercase() {
+        DefaultPasswordRuleConf pwdRuleConf = ImplementationManagerTest.createBaseDefaultPasswordRuleConf();
+        pwdRuleConf.setUppercase(1);
+        TestImplementation passwordRule = new TestImplementation();
+        passwordRule.setBody(POJOHelper.serialize(pwdRuleConf));
+
+        String generatedPassword = passwordGenerator.generate(List.of(new TestPasswordPolicy(passwordRule)));
+
+        assertTrue(generatedPassword.chars().anyMatch(Character::isUpperCase));
     }
 
     @Test
-    public void incopatiblePolicies() {
-        assertThrows(InvalidPasswordRuleConf.class, () -> {
-            DefaultPasswordRuleConf pwdRuleConf1 = createBaseDefaultPasswordRuleConf();
-            pwdRuleConf1.setMinLength(12);
-            TestImplementation passwordRule1 = new TestImplementation();
-            passwordRule1.setBody(POJOHelper.serialize(pwdRuleConf1));
-            TestPasswordPolicy policy1 = new TestPasswordPolicy();
-            policy1.add(passwordRule1);
-
-            DefaultPasswordRuleConf pwdRuleConf2 = createBaseDefaultPasswordRuleConf();
-            pwdRuleConf2.setMaxLength(10);
-            TestImplementation passwordRule2 = new TestImplementation();
-            passwordRule2.setBody(POJOHelper.serialize(pwdRuleConf2));
-            TestPasswordPolicy policy2 = new TestPasswordPolicy();
-            policy2.add(passwordRule2);
-
-            List<PasswordPolicy> policies = new ArrayList<>();
-            policies.add(policy1);
-            policies.add(policy2);
-            passwordGenerator.generate(policies);
-        });
+    public void special() {
+        DefaultPasswordRuleConf pwdRuleConf = ImplementationManagerTest.createBaseDefaultPasswordRuleConf();
+        pwdRuleConf.setSpecial(1);
+        pwdRuleConf.getSpecialChars().add('@');
+        pwdRuleConf.getSpecialChars().add('!');
+        pwdRuleConf.getSpecialChars().add('%');
+        TestImplementation passwordRule = new TestImplementation();
+        passwordRule.setBody(POJOHelper.serialize(pwdRuleConf));
+
+        String generatedPassword = passwordGenerator.generate(List.of(new TestPasswordPolicy(passwordRule)));
+
+        assertTrue(generatedPassword.chars().anyMatch(c -> '@' == c || '!' == c || '%' == c));
     }
 
     @Test
     public void issueSYNCOPE678() {
-        String password = null;
-        try {
-            password = passwordGenerator.generate(Collections.<PasswordPolicy>emptyList());
-        } catch (InvalidPasswordRuleConf e) {
-            fail(e::getMessage);
-        }
+        String password = passwordGenerator.generate(List.of());
         assertNotNull(password);
 
-        DefaultPasswordRuleConf pwdRuleConf1 = createBaseDefaultPasswordRuleConf();
+        DefaultPasswordRuleConf pwdRuleConf1 = ImplementationManagerTest.createBaseDefaultPasswordRuleConf();
         pwdRuleConf1.setMinLength(0);
-        TestImplementation passwordRule1 = new TestImplementation();
-        passwordRule1.setBody(POJOHelper.serialize(pwdRuleConf1));
-        TestPasswordPolicy policy1 = new TestPasswordPolicy();
-
-        password = null;
-        try {
-            password = passwordGenerator.generate(Collections.<PasswordPolicy>singletonList(policy1));
-        } catch (InvalidPasswordRuleConf e) {
-            fail(e::getMessage);
-        }
+        TestImplementation passwordRule = new TestImplementation();
+        passwordRule.setBody(POJOHelper.serialize(pwdRuleConf1));
+
+        password = passwordGenerator.generate(List.of(new TestPasswordPolicy(passwordRule)));
         assertNotNull(password);
     }
 }
diff --git a/core/spring/src/test/java/org/apache/syncope/core/spring/security/TestPasswordPolicy.java b/core/spring/src/test/java/org/apache/syncope/core/spring/security/TestPasswordPolicy.java
index 33ce37a451..521b98dbff 100644
--- a/core/spring/src/test/java/org/apache/syncope/core/spring/security/TestPasswordPolicy.java
+++ b/core/spring/src/test/java/org/apache/syncope/core/spring/security/TestPasswordPolicy.java
@@ -29,6 +29,10 @@ public class TestPasswordPolicy implements PasswordPolicy {
 
     private final List<Implementation> rules = new ArrayList<>();
 
+    public TestPasswordPolicy(final Implementation rule) {
+        rules.add(rule);
+    }
+
     @Override
     public String getKey() {
         return "";
@@ -72,6 +76,5 @@ public class TestPasswordPolicy implements PasswordPolicy {
     @Override
     public List<? extends Implementation> getRules() {
         return this.rules;
-
     }
 }
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
index 25a5186748..bb99f27d83 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
@@ -20,7 +20,6 @@ package org.apache.syncope.fit.core.reference;
 
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -86,76 +85,46 @@ public class ITImplementationLookup implements ImplementationLookup {
 
     private static final Logger LOG = LoggerFactory.getLogger(ITImplementationLookup.class);
 
-    private static final Set<Class<?>> JWTSSOPROVIDER_CLASSES = new HashSet<>(
-            List.of(SyncopeJWTSSOProvider.class, CustomJWTSSOProvider.class));
+    private static final Set<Class<?>> JWTSSOPROVIDER_CLASSES =
+            Set.of(SyncopeJWTSSOProvider.class, CustomJWTSSOProvider.class);
 
     private static final Map<Class<? extends ReportletConf>, Class<? extends Reportlet>> REPORTLET_CLASSES =
-            new HashMap<>() {
-
-        private static final long serialVersionUID = 3109256773218160485L;
-
-        {
-            put(AuditReportletConf.class, AuditReportlet.class);
-            put(ReconciliationReportletConf.class, ReconciliationReportlet.class);
-            put(GroupReportletConf.class, GroupReportlet.class);
-            put(UserReportletConf.class, UserReportlet.class);
-            put(StaticReportletConf.class, StaticReportlet.class);
-        }
-    };
+            Map.of(
+                    AuditReportletConf.class, AuditReportlet.class,
+                    ReconciliationReportletConf.class, ReconciliationReportlet.class,
+                    GroupReportletConf.class, GroupReportlet.class,
+                    UserReportletConf.class, UserReportlet.class,
+                    StaticReportletConf.class, StaticReportlet.class);
 
     private static final Map<Class<? extends AccountRuleConf>, Class<? extends AccountRule>> ACCOUNT_RULE_CLASSES =
-            new HashMap<>() {
-
-        private static final long serialVersionUID = 3109256773218160485L;
-
-        {
-            put(TestAccountRuleConf.class, TestAccountRule.class);
-            put(DefaultAccountRuleConf.class, DefaultAccountRule.class);
-        }
-    };
+            Map.of(
+                    TestAccountRuleConf.class, TestAccountRule.class,
+                    DefaultAccountRuleConf.class, DefaultAccountRule.class);
 
     private static final Map<Class<? extends PasswordRuleConf>, Class<? extends PasswordRule>> PASSWORD_RULE_CLASSES =
-            new HashMap<>() {
-
-        private static final long serialVersionUID = -6624291041977583649L;
-
-        {
-            put(TestPasswordRuleConf.class, TestPasswordRule.class);
-            put(DefaultPasswordRuleConf.class, DefaultPasswordRule.class);
-            put(HaveIBeenPwnedPasswordRuleConf.class, HaveIBeenPwnedPasswordRule.class);
-        }
-    };
+            Map.of(
+                    TestPasswordRuleConf.class, TestPasswordRule.class,
+                    DefaultPasswordRuleConf.class, DefaultPasswordRule.class,
+                    HaveIBeenPwnedPasswordRuleConf.class, HaveIBeenPwnedPasswordRule.class);
 
     private static final Map<
             Class<? extends PullCorrelationRuleConf>, Class<? extends PullCorrelationRule>> PULL_CR_CLASSES =
-            new HashMap<>() {
-
-        private static final long serialVersionUID = 3109256773218160485L;
-
-        {
-            put(DummyPullCorrelationRuleConf.class, DummyPullCorrelationRule.class);
-            put(DefaultPullCorrelationRuleConf.class, DefaultPullCorrelationRule.class);
-            put(LinkedAccountSamplePullCorrelationRuleConf.class, LinkedAccountSamplePullCorrelationRule.class);
-        }
-    };
+            Map.of(
+                    DummyPullCorrelationRuleConf.class, DummyPullCorrelationRule.class,
+                    DefaultPullCorrelationRuleConf.class, DefaultPullCorrelationRule.class,
+                    LinkedAccountSamplePullCorrelationRuleConf.class, LinkedAccountSamplePullCorrelationRule.class);
 
     private static final Map<
             Class<? extends PushCorrelationRuleConf>, Class<? extends PushCorrelationRule>> PUSH_CR_CLASSES =
-            new HashMap<>() {
-
-        private static final long serialVersionUID = 3109256773218160485L;
-
-        {
-            put(DummyPushCorrelationRuleConf.class, DummyPushCorrelationRule.class);
-            put(DefaultPushCorrelationRuleConf.class, DefaultPushCorrelationRule.class);
-        }
-    };
+            Map.of(
+                    DummyPushCorrelationRuleConf.class, DummyPushCorrelationRule.class,
+                    DefaultPushCorrelationRuleConf.class, DefaultPushCorrelationRule.class);
 
-    private static final Set<Class<?>> AUDITAPPENDER_CLASSES = new HashSet<>(
-            List.of(TestFileAuditAppender.class, TestFileRewriteAuditAppender.class));
+    private static final Set<Class<?>> AUDITAPPENDER_CLASSES =
+            Set.of(TestFileAuditAppender.class, TestFileRewriteAuditAppender.class);
 
-    private static final Set<Class<?>> PROVISION_SORTER_CLASSES = new HashSet<>(
-            List.of(DefaultProvisionSorter.class));
+    private static final Set<Class<?>> PROVISION_SORTER_CLASSES =
+            Set.of(DefaultProvisionSorter.class);
 
     private static final Map<String, Set<String>> CLASS_NAMES = new HashMap<>() {
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 80aba2b058..661fbe07d0 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -779,7 +779,7 @@ public abstract class AbstractITCase {
             attributes.forEach((key, value) -> items.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                     new BasicAttribute(key, value))));
 
-            ctx.modifyAttributes(objectDn, items.toArray(new ModificationItem[] {}));
+            ctx.modifyAttributes(objectDn, items.toArray(ModificationItem[]::new));
         } catch (Exception e) {
             LOG.error("While updating {} with {}", objectDn, attributes, e);
         } finally {
diff --git a/pom.xml b/pom.xml
index 6e547d801d..f8e0fec48e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1864,6 +1864,12 @@ under the License.
         <version>${nimbus-jose-jwt.version}</version>
       </dependency>
 
+      <dependency>
+        <groupId>org.passay</groupId>
+        <artifactId>passay</artifactId>
+        <version>1.6.1</version>
+      </dependency>
+
       <dependency>
         <groupId>org.jsoup</groupId>
         <artifactId>jsoup</artifactId>
diff --git a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
index 98f2949d1b..6712428411 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
@@ -122,7 +122,7 @@ public class SecurityConfig {
                 registrationId(SRAProperties.AMType.OIDC.name()).
                 clientId(props.getOidc().getClientId()).
                 clientSecret(props.getOidc().getClientSecret()).
-                scope(props.getOidc().getScopes().toArray(new String[0])).
+                scope(props.getOidc().getScopes().toArray(String[]::new)).
                 build();
     }
 
@@ -177,7 +177,7 @@ public class SecurityConfig {
                 userNameAttributeName(props.getOauth2().getUserNameAttributeName()).
                 clientId(props.getOauth2().getClientId()).
                 clientSecret(props.getOauth2().getClientSecret()).
-                scope(props.getOauth2().getScopes().toArray(new String[0])).
+                scope(props.getOauth2().getScopes().toArray(String[]::new)).
                 authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).
                 jwkSetUri(props.getOauth2().getJwkSetUri()).
                 build();
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
index 7cce9f7f59..81df1a1a57 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
@@ -87,7 +87,7 @@ public class ServerWebExchangeContext implements WebContext {
                 forEach((key, value) -> params.put(key, new String[] { value.toString() }));
 
         if (this.form != null) {
-            form.forEach((key, values) -> params.put(key, values.toArray(new String[0])));
+            form.forEach((key, values) -> params.put(key, values.toArray(String[]::new)));
         }
 
         return params;
diff --git a/src/main/asciidoc/reference-guide/concepts/policies.adoc b/src/main/asciidoc/reference-guide/concepts/policies.adoc
index 576d8385c9..51746fdce8 100644
--- a/src/main/asciidoc/reference-guide/concepts/policies.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/policies.adoc
@@ -216,32 +216,25 @@ endif::[]
 ifeval::["{snapshotOrRelease}" == "snapshot"]
 https://github.com/apache/syncope/blob/master/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPasswordRuleConf.java[DefaultPasswordRuleConf^]
 endif::[]
-) contains the following controls:
-
-* maximum length - the maximum length to allow; `0` means no limit set;
-* minimum length - the minimum length to allow; `0` means no limit set;
-* non-alphanumeric required
-* alphanumeric required
-* digit required
-* lowercase required
-* uppercase required
-* must start with digit
-* must not start with digit
-* must end with digit
-* must not end with digit
-* must start with alphanumeric
-* must start with non-alphanumeric
-* must not start with alphanumeric
-* must not start with non-alphanumeric
-* must end with alphanumeric
-* must end with non-alphanumeric
-* must not end with alphanumeric
-* must not end with non-alphanumeric
-* username allowed - whether a username value can be used
+) is based on https://www.passay.org/[Passay^] and contains the following controls:
+
+* maximum length - the maximum length to allow (`0` means no limit set);
+* minimum length - the minimum length to allow (`0` means no limit set);
+* alphabetical - the number of alphabetical characters required;
+* uppercase - the number of uppercase characters required;
+* lowercase - the number of lowercase characters required;
+* digit - the number of digits required;
+* special - the number of special characters required;
+* special chars - the set of special characters allowed;
+* illegal chars - the set of characters not allowed;
+* repeat same - the size of the longest sequence of repeating characters allowed;
+* username allowed - whether a username value can be used;
 * words not permitted - list of words that cannot be present, even as a substring;
 * schemas not permitted - list of <<schema,schemas>> whose values cannot be present, even as a substring;
-* prefixes not permitted - list of strings that cannot be present as a prefix;
-* suffixes not permitted - list of strings that cannot be present as a suffix.
+
+[TIP]
+The default password rule can be extended to cover specific needs, relying on the
+https://www.passay.org/reference/[whole set of features^] provided by Passay.
 
 [NOTE]
 Before being able to configure the default password rule as mentioned above, you will need to first create a `JAVA`