You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring环境下Modal验证失效:页面刷新导致弹窗消失

问题解决:登录注册弹窗提交后重载丢失错误提示的问题

问题背景

为博客网站开发登录和注册功能时,点击登录/注册按钮会触发页面重载,导致弹窗关闭,无法在弹窗内展示「字符过少」这类错误提示信息。尝试过AJAX但未成功,希望错误信息能显示在弹窗对应输入项下方。

现有代码的核心问题

  1. 登录接口:无论成功失败都返回redirect:/home,重定向会丢失Model中的错误信息,页面重载后弹窗也不会重新打开。
  2. 注册接口:校验失败时返回index页面,虽然尝试添加showModal属性,但页面重载后无法自动唤起注册弹窗,且原有表单输入会丢失。
  3. 前端表单:默认的同步提交会导致页面刷新,弹窗必然关闭,无法保留上下文展示错误。

解决方案:前后端配合实现AJAX异步提交

第一步:修改后端接口,返回JSON格式的校验结果

登录接口修改

@PostMapping("/login")
@ResponseBody
public Map<String, Object> login(@RequestParam String username, @RequestParam String password, HttpServletRequest request) {
    Map<String, Object> result = new HashMap<>();
    Authentication authentication;
    try {
        authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(username, password)
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
        result.put("success", true);
        result.put("message", "登录成功");
    } catch (BadCredentialsException e) {
        result.put("success", false);
        result.put("error", "用户名或密码无效");
    }
    return result;
}

说明:直接处理登录逻辑,返回JSON结果,避免重定向;如果使用Spring Security默认登录流程,也可以自定义失败处理器返回JSON。

注册接口修改

@PostMapping("/register")
@ResponseBody
public Map<String, Object> registration(@ModelAttribute("userForm") User userForm, BindingResult bindingResult) {
    Map<String, Object> result = new HashMap<>();
    userValidator.validate(userForm, bindingResult);

    if (bindingResult.hasErrors()) {
        // 收集所有字段的错误信息
        Map<String, String> errors = new HashMap<>();
        bindingResult.getFieldErrors().forEach(error -> {
            errors.put(error.getField(), error.getDefaultMessage());
        });
        result.put("success", false);
        result.put("fieldErrors", errors);
        return result;
    }

    userService.save(userForm);
    result.put("success", true);
    result.put("message", "注册成功");
    return result;
}

说明:校验失败时,将每个字段的错误信息以键值对形式返回,前端可根据字段名对应到输入框下方显示。

补充:配置国际化错误消息(对应Validation中的错误码)

在messages.properties中添加错误提示文本:

NotEmpty.userForm.username=用户名不能为空
Size.userForm.username=用户名长度需在2-32位之间
Duplicate.userForm.username=该用户名已被注册
NotEmpty.userForm.password=密码不能为空
Size.userForm.password=密码长度需在2-32位之间
Diff.userForm.passwordConfirm=两次输入的密码不一致

第二步:前端修改为AJAX异步提交表单

先添加CSRF元标签(页面头部)

<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_parameter" th:content="${_csrf.parameterName}"/>

登录弹窗JS处理

<script>
$("#loginForm").submit(function(e) {
    e.preventDefault(); // 阻止默认表单提交
    const formData = new FormData(this);
    // 携带CSRF令牌
    formData.append($("meta[name='_csrf_parameter']").attr("content"), $("meta[name='_csrf']").attr("content"));

    $.ajax({
        url: $(this).attr("action"),
        type: "POST",
        data: formData,
        processData: false,
        contentType: false,
        success: function(res) {
            if (res.success) {
                window.location.href = "/home"; // 登录成功后跳转首页
            } else {
                // 显示登录错误信息
                $("#loginModal .has-error").text(res.error).show();
            }
        },
        error: function() {
            $("#loginModal .has-error").text("服务器错误,请稍后重试").show();
        }
    });
});
</script>

注册弹窗JS处理

<script>
$("#registrationForm").submit(function(e) {
    e.preventDefault();
    const formData = new FormData(this);
    formData.append($("meta[name='_csrf_parameter']").attr("content"), $("meta[name='_csrf']").attr("content"));

    $.ajax({
        url: $(this).attr("action"),
        type: "POST",
        data: formData,
        processData: false,
        contentType: false,
        success: function(res) {
            if (res.success) {
                $("#registerModal").modal("hide");
                // 自动唤起登录弹窗提示用户登录
                $("#loginModal").modal("show");
            } else {
                // 清空之前的错误提示
                $("#registerModal .has-error").text("").hide();
                // 遍历字段错误,显示对应位置
                Object.keys(res.fieldErrors).forEach(field => {
                    const errorText = res.fieldErrors[field];
                    $("#registerModal [th\\:field='*{" + field + "}']").next(".has-error").text(errorText).show();
                });
            }
        },
        error: function() {
            $("#registerModal .has-error").first().text("服务器错误,请稍后重试").show();
        }
    });
});
</script>

第三步:调整前端错误提示元素的默认状态

将弹窗内的错误提示元素默认设置为隐藏,避免页面加载时显示空标签:

<!-- 登录弹窗错误提示 -->
<p class="has-error" th:text="${error}" style="color: red; display: none;"></p>

<!-- 注册弹窗错误提示 -->
<p class="has-error" th:if="${#fields.hasErrors('username')}" th:errors="*{username}" style="color: red; display: none;"></p>
<p class="has-error" th:if="${#fields.hasErrors('password')}" th:errors="*{password}" style="color: red; display: none;"></p>
<span class="has-error" th:if="${#fields.hasErrors('passwordConfirm')}" th:errors="*{passwordConfirm}" style="color: red; display: none;"></span>

关键说明

  1. AJAX阻止默认提交:通过e.preventDefault()避免页面重载,弹窗得以保留,保证错误提示在原上下文展示。
  2. 后端返回结构化JSON:登录返回全局错误,注册返回字段级错误,前端可精准对应到输入框下方显示。
  3. CSRF令牌处理:Spring Security默认启用CSRF校验,AJAX提交必须携带令牌,否则请求会被拒绝。

内容的提问来源于stack exchange,提问作者jCodeXtreme

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 03:00:57