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

介绍

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

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

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

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

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

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

注册表格

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

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

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.mynotes.spring.mvc</groupId>
    <artifactId>password-strength</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>password-strength</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.passay</groupId>
            <artifactId>passay</artifactId>
            <version>1.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

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

public class UserDto {

    @NotEmpty
    private String name;

    @Email
    @NotEmpty
    private String email;

    private String password;

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

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

@Controller
@RequestMapping("/signup")
public class SignUpController {

    @ModelAttribute("user")
    public UserDto userDto() {
        return new UserDto();
    }

    @GetMapping
    public String showForm() {
        return "signup";
    }

    @PostMapping
    public String submitForm(@Valid @ModelAttribute("user") UserDto user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "signup";
        }
        return "success";
    }

}

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

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

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

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

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

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

<form action="#" th:action="@{/signup}" th:object="${user}" method="post">

    <div class="form-group">
        <input type="text" th:field="*{name}" class="form-control"
               id="name" placeholder="Name"> <span
               th:if="${#fields.hasErrors('name')}" th:errors="*{name}"
               class="text-danger"></span>
     </div>
     <div class="form-group">
        <input type="text" th:field="*{email}" class="form-control"
               id="email" placeholder="Email"> <span
               th:if="${#fields.hasErrors('email')}" th:errors="*{email}"
               class="text-danger"></span>
     </div>
     <div class="form-group">
         <input type="text" th:field="*{password}" class="form-control"
                id="password" placeholder="Password">
         <ul class="text-danger" th:each="error: ${#fields.errors('password')}">
             <li th:each="message : ${error.split(',')}">
                 <p class="error-message" th:text="${message}"></p>
             </li>
         </ul>
     </div>

     <div class="col-md-6 mt-5">
         <input type="submit" class="btn btn-primary" value="Submit">
     </div>
</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

@Documented
@Constraint(validatedBy = PasswordConstraintValidator.class)
@Target({ FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface ValidPassword {

    String message() default "Invalid Password";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

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

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

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

@ValidPassword
private String password;

自定义密码约束验证程序

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

<dependency>
    <groupId>org.passay</groupId>
    <artifactId>passay</artifactId>
    <version>{$version}</version>
</dependency>

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

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

public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {

    @Override
    public void initialize(ValidPassword arg0) {
    }

    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        PasswordValidator validator = new PasswordValidator(Arrays.asList(
            // at least 8 characters
            new LengthRule(8, 30),

            // at least one upper-case character
            new CharacterRule(EnglishCharacterData.UpperCase, 1),

            // at least one lower-case character
            new CharacterRule(EnglishCharacterData.LowerCase, 1),

            // at least one digit character
            new CharacterRule(EnglishCharacterData.Digit, 1),

            // at least one symbol (special character)
            new CharacterRule(EnglishCharacterData.Special, 1),

            // no whitespace
            new WhitespaceRule()

        ));
        RuleResult result = validator.validate(new PasswordData(password));
        if (result.isValid()) {
            return true;
        }
        List<String> messages = validator.getMessages(result);

        String messageTemplate = messages.stream()
            .collect(Collectors.joining(","));
        context.buildConstraintViolationWithTemplate(messageTemplate)
            .addConstraintViolation()
            .disableDefaultConstraintViolation();
        return false;
    }
}

我们实现了接口,这迫使我们实现几个方法。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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值