• 使用Passay库为Spring Boot Thymeleaf Web应用自定义密码策略验证


    介绍

    如今,密码策略非常普遍,并且存在于大多数在线平台上。虽然某些用户并不真正喜欢它们,但它们存在是有原因的——使密码更安全。

    您肯定有过应用程序强制使用某些密码规则的经验,例如允许的最小或最大字符数,包括数字,大写字母等。

    无论安全系统有多好,如果用户选择“密码”等弱密码,敏感数据可能会暴露。虽然一些用户可能会对密码策略感到恼火,但它们可以保护用户数据的安全,因为它会使攻击效率低下。

    为了在基于Spring的应用程序中实现这一点,我们将使用Passay - 一个专门为此目的制作的库,它使在Java中执行密码策略变得容易。

    请注意:本教程假设您具有 Spring 框架的基本知识,因此为了简洁起见,我们将更多地关注 Passay。

    除了密码策略之外,实现安全性的一个良好且基本的技术是密码编码

    注册表格

    与往常一样,从框架式 Spring Boot 项目开始的最简单方法是使用Spring Initializr

    选择您喜欢的 Spring 引导版本并添加依赖项:Web和Thymeleaf

    在此之后,将其生成为 Maven 项目,一切就绪!

    1. "1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <modelVersion>4.0.0modelVersion>
    6. <parent>
    7. <groupId>org.springframework.bootgroupId>
    8. <artifactId>spring-boot-starter-parentartifactId>
    9. <version>2.7.5version>
    10. <relativePath />
    11. parent>
    12. <groupId>com.mynotes.spring.mvcgroupId>
    13. <artifactId>password-strengthartifactId>
    14. <version>0.0.1-SNAPSHOTversion>
    15. <name>password-strengthname>
    16. <description>Demo project for Spring Bootdescription>
    17. <properties>
    18. <java.version>1.8java.version>
    19. properties>
    20. <dependencies>
    21. <dependency>
    22. <groupId>org.springframework.bootgroupId>
    23. <artifactId>spring-boot-starter-thymeleafartifactId>
    24. dependency>
    25. <dependency>
    26. <groupId>org.springframework.bootgroupId>
    27. <artifactId>spring-boot-starter-webartifactId>
    28. dependency>
    29. <dependency>
    30. <groupId>org.passaygroupId>
    31. <artifactId>passayartifactId>
    32. <version>1.6.2version>
    33. dependency>
    34. <dependency>
    35. <groupId>org.springframework.bootgroupId>
    36. <artifactId>spring-boot-devtoolsartifactId>
    37. <scope>runtimescope>
    38. dependency>
    39. <dependency>
    40. <groupId>org.springframework.bootgroupId>
    41. <artifactId>spring-boot-starter-testartifactId>
    42. <scope>testscope>
    43. dependency>
    44. <dependency>
    45. <groupId>org.springframework.bootgroupId>
    46. <artifactId>spring-boot-starter-validationartifactId>
    47. dependency>
    48. <dependency>
    49. <groupId>junitgroupId>
    50. <artifactId>junitartifactId>
    51. <version>4.13.2version>
    52. <scope>testscope>
    53. dependency>
    54. dependencies>
    55. <build>
    56. <plugins>
    57. <plugin>
    58. <groupId>org.springframework.bootgroupId>
    59. <artifactId>spring-boot-maven-pluginartifactId>
    60. plugin>
    61. plugins>
    62. build>
    63. project>

    让我们定义一个简单的数据传输对象,其中我们将包含要从表单中捕获的所有属性:

    1. public class UserDto {
    2. @NotEmpty
    3. private String name;
    4. @Email
    5. @NotEmpty
    6. private String email;
    7. private String password;

    我们尚未对密码字段进行批注,因为我们将为此实现自定义批注。

    然后我们有一个简单的控制器类,它服务于注册表单,并在使用映射提交时捕获其数据:GET/POST

    1. @Controller
    2. @RequestMapping("/signup")
    3. public class SignUpController {
    4. @ModelAttribute("user")
    5. public UserDto userDto() {
    6. return new UserDto();
    7. }
    8. @GetMapping
    9. public String showForm() {
    10. return "signup";
    11. }
    12. @PostMapping
    13. public String submitForm(@Valid @ModelAttribute("user") UserDto user, BindingResult bindingResult) {
    14. if (bindingResult.hasErrors()) {
    15. return "signup";
    16. }
    17. return "success";
    18. }
    19. }

    我们首先定义了 a,并为其分配了实例。这是提交后将保存信息的对象。@ModelAttribute("user")UserDto

    使用此对象,我们可以提取数据并将其持久保存在数据库中。

    该方法返回一个值为“注册”的字符串。由于我们的类路径中有Thymeleaf,Spring 将在资源中的模板文件夹中搜索“注册.html”。showForm()

    同样,我们有一个POST映射,它将检查表单是否有任何错误。如果是这样,它将重定向回“注册.html”页面。否则,它会将用户转发到“成功”页面。submitForm()

    Thymeleaf是一个现代的服务器端Java模板引擎,用于处理和创建HTML,XML,JavaScript,CSS和文本。它是Java服务器页面(JSP)等旧模板引擎的现代替代方案。

    让我们继续定义一个“注册.html”页面:

    1. <form action="#" th:action="@{/signup}" th:object="${user}" method="post">
    2. <div class="form-group">
    3. <input type="text" th:field="*{name}" class="form-control"
    4. id="name" placeholder="Name"> <span
    5. th:if="${#fields.hasErrors('name')}" th:errors="*{name}"
    6. class="text-danger"></span>
    7. </div>
    8. <div class="form-group">
    9. <input type="text" th:field="*{email}" class="form-control"
    10. id="email" placeholder="Email"> <span
    11. th:if="${#fields.hasErrors('email')}" th:errors="*{email}"
    12. class="text-danger"></span>
    13. </div>
    14. <div class="form-group">
    15. <input type="text" th:field="*{password}" class="form-control"
    16. id="password" placeholder="Password">
    17. <ul class="text-danger" th:each="error: ${#fields.errors('password')}">
    18. <li th:each="message : ${error.split(',')}">
    19. <p class="error-message" th:text="${message}"></p>
    20. </li>
    21. </ul>
    22. </div>
    23. <div class="col-md-6 mt-5">
    24. <input type="submit" class="btn btn-primary" value="Submit">
    25. </div>
    26. </form>

    这里有几点应该指出:

    • th:action = "@{/signup}"- 操作属性是指我们在提交表单时调用的 URL。我们以控制器中的“注册”URL 映射为目标。
    • method="post"- 方法属性是指我们发送的请求类型。这必须与方法中定义的请求类型匹配。submitForm()
    • th:object="${user}"- 对象属性是指我们之前在控制器中使用的对象名称。使用窗体的其余部分,我们将填充实例的字段,然后保存实例。@ModelAttribute("user")UserDto

    我们还有其他 3 个映射到、和usingtag的输入字段。如果字段有错误,将通过标签通知用户。nameemailpasswordth:fieldth:errors

    让我们运行我们的应用程序并导航到http://localhost:8080/signup

    自定义@ValidPassword注释

    根据项目要求,我们有时必须为应用程序定义特定的自定义代码。

    由于我们可以强制实施不同的策略和规则,因此让我们继续定义一个自定义注释来检查有效的密码,我们将在类中使用该注释。UserDto

    批注只是代码的元数据,不包含任何业务逻辑。它们只能提供有关定义它的属性(类/方法/包/字段)的信息。

    让我们创建注释:@ValidPassword

    1. @Documented
    2. @Constraint(validatedBy = PasswordConstraintValidator.class)
    3. @Target({ FIELD, ANNOTATION_TYPE })
    4. @Retention(RUNTIME)
    5. public @interface ValidPassword {
    6. String message() default "Invalid Password";
    7. Class[] groups() default {};
    8. Class[] payload() default {};
    9. }

    如您所见,要创建注释,我们使用关键字。让我们看一下几个关键字并充分理解它们,然后再继续:@interface

    • @Documented:一个简单的标记注释,用于判断是否在Javadocs中添加注释。
    • @Constraint:将批注标记为Bean 验证约束。该元素指定实现约束的类。稍后我们将创建该类。validatedByPasswordConstraintValidator
    • @Target:是可以使用我们的注释的地方。如果未指定此项,则可以将批注放置在任何位置。目前,我们的注释可以放置在实例变量和其他注释上。
    • @Retention:定义批注应保留多长时间。我们已经选择了它可以被运行时环境使用。RUNTIME

    要在我们的类中使用它,只需注释密码字段:UserDto

    1. @ValidPassword
    2. private String password;

    自定义密码约束验证程序

    现在我们有了注释,让我们实现它的验证逻辑。在此之前,请确保您的pom.xml文件中包含Passay Maven依赖项:

    1. <dependency>
    2. <groupId>org.passay</groupId>
    3. <artifactId>passay</artifactId>
    4. <version>{$version}</version>
    5. </dependency>

    您可以在此处检查最新的依赖项。

    最后,让我们编写我们的类:PasswordConstraintValidator

    1. public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {
    2. @Override
    3. public void initialize(ValidPassword arg0) {
    4. }
    5. @Override
    6. public boolean isValid(String password, ConstraintValidatorContext context) {
    7. PasswordValidator validator = new PasswordValidator(Arrays.asList(
    8. // at least 8 characters
    9. new LengthRule(8, 30),
    10. // at least one upper-case character
    11. new CharacterRule(EnglishCharacterData.UpperCase, 1),
    12. // at least one lower-case character
    13. new CharacterRule(EnglishCharacterData.LowerCase, 1),
    14. // at least one digit character
    15. new CharacterRule(EnglishCharacterData.Digit, 1),
    16. // at least one symbol (special character)
    17. new CharacterRule(EnglishCharacterData.Special, 1),
    18. // no whitespace
    19. new WhitespaceRule()
    20. ));
    21. RuleResult result = validator.validate(new PasswordData(password));
    22. if (result.isValid()) {
    23. return true;
    24. }
    25. List<String> messages = validator.getMessages(result);
    26. String messageTemplate = messages.stream()
    27. .collect(Collectors.joining(","));
    28. context.buildConstraintViolationWithTemplate(messageTemplate)
    29. .addConstraintViolation()
    30. .disableDefaultConstraintViolation();
    31. return false;
    32. }
    33. }

    我们实现了接口,这迫使我们实现几个方法。ConstraintValidator

    我们首先通过传递要在密码中强制执行的约束数组来创建对象。PasswordValidator

    约束是不言自明的:

    • 它的长度必须介于 8 到 30 个字符之间,由LengthRule
    • 它必须至少具有 1 个小写字符,如CharacterRule
    • 它必须至少具有 1 个大写字符,如CharacterRule
    • 它必须至少具有 1 个由CharacterRule
    • 它必须至少具有 1 位数字字符,如CharacterRule
    • 它不得包含由WhitespaceRule

    可以使用Passay编写的规则的完整列表可以在官方网站上找到。

    最后,我们验证了密码,如果它通过了所有条件,则返回。如果某些条件失败,我们将所有失败条件的错误消息聚合到一个字符串中,以“,”分隔,然后将其放入 and 中返回。truecontextfalse

    让我们再次运行我们的项目并输入无效的密码以验证验证是否有效:

    结论

    在本文中,我们介绍了如何使用Passay库强制执行某些密码规则。我们为此创建了一个自定义注释和密码约束验证器,并在我们的实例变量中使用它,实际的业务逻辑是在单独的类中实现的。

    示例代码下载:https://github.com/allwaysoft/spring-boot-passay-password-strength

  • 相关阅读:
    易联众智能自动办理平台,AI赋能让数字政务服务“触手可及”
    【ML07】Linear Regression using Scikit-Learn
    Unity Metaverse(一)、Ready Player Me & Blender 自定义你的Avatar虚拟人
    Java 线程池四种拒绝策略
    css 雷达扫描图
    FreeRTOS教程8 任务通知
    InVEST模型在固碳、生境质量、产水等领域案例分析
    redis做缓存(cache)
    Chant 开发人员工作台2022支持转录和单词定位
    实现简易负载式均衡在线判题系统
  • 原文地址:https://blog.csdn.net/allway2/article/details/127777602