之前给出的所有答案都使用相同(正确)的技术来为每个要求使用单独的前瞻。但它们包含一些效率低下和潜在的巨大错误,具体取决于实际使用密码的后端。
我将从接受的答案中的正则表达式开始:
^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\S+$).{8,}$
首先,由于 Java 支持 \A 和 \z,我更喜欢使用它们来确保验证整个字符串,独立于 Pattern.MULTILINE。这不会影响性能,但可以避免在回收正则表达式时出错。
\A(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\S+$).{8,}\z
检查密码是否不包含空格并检查其最小长度可以通过将变量量词{8,} 放在限制允许字符的简写\S 上一次性使用all 来完成:
\A(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])\S{8,}\z
如果提供的密码确实包含空格,则将完成所有检查,但最终检查失败。这可以通过将所有点替换为\S 来避免:
\A(?=\S*[0-9])(?=\S*[a-z])(?=\S*[A-Z])(?=\S*[@#$%^&+=])\S{8,}\z
只有当你真的想允许任何字符时才应该使用点。否则,使用(否定的)字符类将您的正则表达式限制为仅允许的字符。尽管在这种情况下差别不大,not using the dot when something else is more appropriate 是一个非常好的习惯。我看到太多catastrophic backtracking 的案例,因为开发人员懒得使用比点更合适的东西。
由于初始测试很有可能会在密码的前半部分找到合适的字符,因此惰性量词可能更有效:
\A(?=\S*?[0-9])(?=\S*?[a-z])(?=\S*?[A-Z])(?=\S*?[@#$%^&+=])\S{8,}\z
但是现在对于真正重要的问题:没有一个答案提到原始问题似乎是由使用 ASCII 思考的人编写的。但在 Java 中,字符串是 Unicode。密码中是否允许使用非 ASCII 字符?如果是,是只不允许使用 ASCII 空格,还是应该排除所有 Unicode 空格。
默认情况下,\s 只匹配 ASCII 空格,因此它的反向 \S 匹配所有 Unicode 字符(空格与否)和所有非空格 ASCII 字符。如果允许 Unicode 字符但不允许 Unicode 空格,则可以指定 UNICODE_CHARACTER_CLASS 标志以使 \S 排除 Unicode 空格。如果不允许使用 Unicode 字符,则可以使用 [\x21-\x7E] 代替 \S 来匹配所有非空格或控制字符的 ASCII 字符。
这给我们带来了下一个潜在问题:我们是否要允许控制字符?编写正确的正则表达式的第一步是准确指定要匹配的内容和不匹配的内容。唯一在技术上 100% 正确的答案是问题中的密码规范是模棱两可的,因为它没有说明是否允许某些字符范围(如控制字符或非 ASCII 字符)。