如何将客户端验证和服务器端验证同步?

9
通常在编写Web应用程序时,我们希望在客户端执行验证以提供即时反馈,并在服务器端执行验证以确保数据的完整性和安全性。然而,客户端浏览器应用程序通常是用JavaScript编写的。服务器端可以使用Java、Php、Ruby、Python等多种语言编写。如果服务器端基于像node.js这样的东西,那么很容易在客户端和服务器上重用相同的验证代码,但如果服务器端基于Rails或Django(或任何其他你能想到的框架),怎样才能确保验证代码保持同步呢?不把同样的代码在多种语言中重新实现似乎有点冗余。

同样的问题在这里。 - Baterka
4个回答

2
如果您记住以下观点,复制某些验证似乎是可以的。
让我们将验证分为两部分。A)业务验证,例如“如果选中复选框Y,则X字段中的金额应大于500美元”,B)基本数据验证,例如数据类型检查、空值检查等。(我们可以争论每个验证都是业务验证,但这完全取决于上下文)。
分类A:它是您的业务逻辑的一部分,并且应仅保留在服务器端。
分类B:此类验证是放置在客户端的潜在候选项。但请记住,浏览器端验证可以被绕过。这并不意味着您根本不应该在浏览器端进行验证,但此类验证应该被视为仅用于节省从服务器传输数据来回的奖励。服务器必须重新执行这些验证。
简而言之,验证不应被视为跨层可重用代码单元。它们的目标不同,应允许冗余。
希望这有所帮助。

4
问题是“如何最好地保持客户端和服务器端验证同步”。你回答了另一个问题,涉及将业务逻辑(也称为业务验证)放在哪里。 - RMuesi

1

从我看到的项目来看,有三种常见策略:

  1. 完全重复客户端和服务器端验证。如果使用javascript前端和java/c#/ruby后端,则需要两个不同的代码库。您必须手动保持两者的逻辑同步。

  2. 进行最小限度的客户端验证。只检查非常基本的内容。让服务器端完成全部验证。让服务器端传递一种验证错误对象到客户端,并让客户端逻辑将其转换为UI消息(错误消息、红色边框等)。Asp.net MVC框架大致是这种模型的一个例子。

  3. 使用ajax在用户更改或离开每个控件时向服务器端发出验证调用。这可以允许您在服务器端完成所有验证,并减少用户的反馈等待时间,但可能会极大地增加客户端到服务器端的流量。

根据我的经验,选项1通常比维护选项2和3所需的额外代码和复杂性更容易。


0

我们的架构允许共享验证器代码:我们的后端是JAVA。前端是JavaScript/TypeScript(如果有关系,使用Angular)。JAVA后端处理所有业务事务。为了向用户报告MySQL和一些实用程序用户首选项,我们还编写了一个最小的NodeJS/Express后端。

为了重复使用验证,我们将一些端点从JAVA更改为NodeJS,然后NodeJS进行验证并在内部将请求正文传递给JAVA(通过系统调用或非公共API)。然后我们从JAVA代码中删除了验证。NodeJS/Express主要作为代理,它还检查请求的有效性。

这样我们就可以在前端和我们的最小后端之间共享TypeScript代码/类。

增加的开销:

  • 需要单独运行一个NodeJS后端来管理/监视进程
  • 需要公开一种方法来在系统内部调用您的方法/API(并确保它们不是公共的,因为它们将不再具有验证器)
  • 一些额外的Nginx路由

0
我建立了这个服务器端验证端点:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import org.hibernate.validator.HibernateValidator;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.groupingBy;

@RestController
@AllArgsConstructor
public class ValidationEndpoint {
    static {
        Locale.setDefault(Locale.GERMAN);
    }

    private final ObjectMapper objectMapper;
    private final Validator validator;

    @PostMapping("/")
    public Map < String, List < String >> validate(@RequestBody String entity, @RequestParam String className) throws ClassNotFoundException, JsonProcessingException {
        Class c = Class.forName(className);
        Object jsonObject = objectMapper.readValue(entity, c);

        return validator.validate(jsonObject).stream().map(constraintViolation -> {
            Violation violation = new Violation();
            violation.setMessage(constraintViolation.getMessage());
            violation.setFieldName(constraintViolation.getPropertyPath().toString());
            return violation;
        }).collect(groupingBy(Violation::getFieldName, Collectors.mapping(Violation::getMessage, Collectors.toList())));

    }

    @PostMapping("/valida")
    public List < String > validateField(@RequestBody String entity, @RequestParam String className, @RequestParam String propertyName) throws ClassNotFoundException, JsonProcessingException {
        Class c = Class.forName(className);
        Object jsonObject = objectMapper.readValue(entity, c);

        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
            .configure()
            .failFast(false)
            .defaultLocale(Locale.GERMAN)
            .buildValidatorFactory();

        Validator validator = validatorFactory.getValidator();

        return validator.validateProperty(jsonObject, propertyName).stream().map(constraintViolation -> {
            Violation violation = new Violation();
            violation.setMessage(constraintViolation.getMessage());
            violation.setFieldName(constraintViolation.getPropertyPath().toString());
            return violation;
        }).map(Violation::getMessage).toList();
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接