有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java线程安全输入验证

我正在构建一个SpringBoot应用程序,它允许注册、提交各种需要验证的数据等。例如,我有一个带有基本约束的实体设置:

@Entity
public class Account extends BaseEntity {

    public static final int MAX_LENGTH = 64;

    @Column(unique = true, nullable = false, length = MAX_LENGTH)
    private String username;

    @Column(unique = true, nullable = false, length = MAX_LENGTH)
    private String email;

    ...
}

每次创建新帐户之前,都会在服务层上执行验证:

public Account register(String username, String email, String rawPassword) {

    if (!accountValidator.validate(username, email, rawPassword)) {
        return null;
    }

    Account account = new Account(username, email, passwordEncoder.encode(rawPassword));
    try {
        return accountRepository.save(account);
    } catch (DataIntegrityViolationException e) {
        return null;
    }
}

验证程序代码段:

public boolean validate(String username, String email, String password) {

    if (!validateUsernameStructure(username)) {
        return false;
    }

    if (!validateEmailStructure(email)) {
        return false;
    }

    if (!validatePasswordStructure(password)) {
        return false;
    }

    // Performs a query on all users to see if no such email or username
    // already exists. Before checking the email and username are set to
    // lowercase characters on the service layer.
    if (accountService.doesEmailOrUsernameExist(email, username)) {
        return false;
    }
    return true;
}

现在,在这种情况下,如果我收到很多多个请求,其中一个成功地通过了验证,那么如果用户名或电子邮件首先被强制为小写,我将遇到一个异常。但是,例如,我希望允许用户使用大写/小写用户名、电子邮件字符等进行注册。基于this问题,我可以在实体中添加一个附加字段,或者在数据库级别添加一个更复杂的约束,但是我希望这样做时不会出现溢出数据和java代码

例如,我收到两个创建新帐户的请求,间隔几毫秒:

Request 1:
username=foo
email=foo@foo.com

Request 2:
username=foO
email=foO@foO.com

在此阶段,我会检查重复项(电子邮件和用户名设置为小写,但在保存时,我会保持大小写不变):

if (accountService.doesEmailOrUsernameExist(email, username)) {
    return false;
}

但是,由于请求彼此非常接近,第一个请求可能尚未创建新帐户,因此第二个帐户的检查通过,因此我得到两个数据库条目

因此,实际的问题是,如何在我的服务层中对此类操作执行线程安全验证,而不会对性能造成巨大影响

我为本例选择的解决方案

在设置用户名和电子邮件时,还要将这些值应用于它们的小写对应项,并对它们应用唯一的约束。这样,我就可以为这两个属性保留用户设置的大小写:

@Entity
public class Account extends BaseEntity {

    public static final int MAX_LENGTH = 64;

    @Column(unique = true, nullable = false, length = MAX_LENGTH)
    private final String username;

    @Column(unique = true, nullable = false, length = MAX_LENGTH)
    private String email;

    @Column(unique = true, nullable = false, length = MAX_LENGTH)
    private final String normalizedUsername;

    @Column(unique = true, nullable = false, length = MAX_LENGTH)
    private String normalizedEmail;

    public Account(String username, String email) {
        this.username = username;
        this.email = email;

        this.normalizedUsername = username.toLowerCase();
        this.normalizedEmail = email.toLowerCase();
    }

    ...
}

共 (1) 个答案

  1. # 1 楼答案

    没有数据库的帮助就没有简单的解决方案,因为事务是相互隔离的

    另一种方法是将用户名/电子邮件临时存储在内存中,并进行复杂的同步以执行检查。在集群环境中,情况会更加复杂。这非常难看,很难维护(如果单个同步点的锁保持时间过长,可能会显著影响性能)

    标准且简单的解决方案是使用数据库约束