大家怎么防止表单重复提交的?

有没有内置的方法或软件包

《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 27

3L 说了前端的方法,我说说后端的方法吧。
新建一个中间件:

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;

class LimitFormRepeatSubmit extends Middleware
{
     public function handle($request, Closure $next)
    {
        /****************************************
         * 1. 前端自己生成一个 form_token
         * 2. 如果 session 已经存在,则告诉重复提交
         ****************************************/
        $token = $request->input('form_token');
        if (cache()->has($token)) {
            return back()->with('status', '请不要重复提交');
        }

        cache([$token => 'value'], 1);

        return $next($request);
    }
}

前端页面

<form action="">
    <input type="hidden" name="form_token" value="{{ str_random(40) }}">
  </form>
5年前 评论

@zedisdog 不行,这个好像是用来验证表单来源是否为本站的,并没有做重复提交处理

5年前 评论

@深蓝色 那就不知道了,按理说重复提交的问题应该前端来解决。比如,提交按钮按了就不能再按了之类的。

5年前 评论

3L 说了前端的方法,我说说后端的方法吧。
新建一个中间件:

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;

class LimitFormRepeatSubmit extends Middleware
{
     public function handle($request, Closure $next)
    {
        /****************************************
         * 1. 前端自己生成一个 form_token
         * 2. 如果 session 已经存在,则告诉重复提交
         ****************************************/
        $token = $request->input('form_token');
        if (cache()->has($token)) {
            return back()->with('status', '请不要重复提交');
        }

        cache([$token => 'value'], 1);

        return $next($request);
    }
}

前端页面

<form action="">
    <input type="hidden" name="form_token" value="{{ str_random(40) }}">
  </form>
5年前 评论

@DavidNineRoc 有问题,假设前端页面不止一个表单,怎么区分不同表单的提交呢?

5年前 评论

@Littlesqx 解释一下吧,免得更多的人问。

  1. 首先 str_random 总会生成一个唯一的字符串。这就代表我们缓存中的 key 总是唯一的
  2. 永远永远永远表单的提交只能一个一个提交,如果你能做到两个表单同时提交,也许真的有,我不知道而已?(还有,即使两个表单同时提交也很肯定没有问题的。只要这两个表单都放 form_token 字段,并且使用 str:random 生成两个不同的,前面提到了唯一。一百个也没有问题)
    路上下雨,手机码字就暂时写这么多吧!
5年前 评论

@DavidNineRoc 嗯,明白了。还有一个问题,如果有人恶意提交(模拟改变 form_token 的值,不断请求)。那后端就不断地存,session 会不会爆炸?

5年前 评论

@Littlesqx Cache
时间短一点,一起提交过来的还有 csrf_token

5年前 评论

如果使用 Laravel 用户认证 的包,则自动加载了登录限制: 用户认证《Laravel 5.6 中文文档》
或者也可以直接使用路由上的频率限制: 路由《Laravel 5.6 中文文档》

5年前 评论

@DavidNineRoc 这样行不通吧...
前端每次提交都会生成新的 token , 这个新的 token 在你的中间件里面被接收, 所以 if 判断永远都是 false, 做的是无用功啊 :joy:

5年前 评论

@weir 你可能需要看一下题目说的是 防止重复提交,而不是多次提交。
重复提交我的定义是:单次的数据提交多次,更多是出现在 AJAX 提交。比如下订单,你点了,网络可能延迟一秒,前端也没有禁用按钮,然后你以为出 bug,然后一直点。这一秒延迟你点了十次,如果你不做验证,就会有十条订单记录。而如果用我说的那个方式,实际上发到后端的都是 form_token,后面的九次都是重复提交的,直接不进入控制器就行了

5年前 评论

@DavidNineRoc 这个中间件并不能防止你定义的重复提交. 一秒点十次, 这十次前端提交过来的表单都会携带不同的 token , 也就是说你在中间件里面接收到 token 每次都是不一样的 , if 里面的代码永远都不会起作用.

5年前 评论

@DavidNineRoc 你在 if 里面 dd() 测试一下就明白我的意思了

5年前 评论

@weir 我写的代码我不清楚,我不知道你为什么会有十次 token 不同?为什么点击十次就会有十个 token?你觉得 token 是在什么时候生成的? 素质三问。

5年前 评论

@DavidNineRoc 不要激动啊
楼主并没有指定表单的环境是浏览器还是其它的, web 上我没有测试 , 也许可以 . 我在小程序里面试了一下, 确实不起作用.

5年前 评论

首先

这个和小程序什么没有任何关系。
你在页面加载的时候生成一个 form_token,只在页面加载时生成,那么其他时候就只会用这一个,那么只要你没有刷新页面,那么就永远不会变,当你提交的带上这个参数提交到后台。 那么你是觉得为什么做不到呢?

5年前 评论

@DavidNineRoc 好吧, 是我错了. 没搞清楚你说的重复提交和多次提交的区别. 我测试的时候用了一个表单, 提交相同的内容, 按照你的定义, 属于 多次提交 .

5年前 评论

有办法直接利用内置的那个token吗,感觉弄两个名称相似的Token好诡异

5年前 评论

1 如果防止表单重复提交我觉得csrf是比较好的方式
2 如果防止数据重复我觉得可以可以在数据库上加上唯一索引 (你觉得要重复的的数据) 然遇到重复的就会抛出异常 这个有点暴力 但是能解决问题 也可以考虑redis存md5

5年前 评论

csrf并没有提供重复提交的机制

5年前 评论

我觉得比较好的做法是通过中间件,请求到来的时候查询redis中是否有根据请求参数、请求路径、ip等变量拼接后加密的md5值(或其他可以唯一识别的值),如果存在,判定为重复请求,如果不存在就往redis中新增,中间件通过后,执行完业务,再通过Terminable中间件将这个值从redis中删除

5年前 评论

@DavidNineRoc 老铁这个方法会不会被人恶意提交 session 被干爆啊

4年前 评论

这个方法其实可以实现这个功能,需要做一点修改,将前端token记录到cache,下次请求比对两次token是否相等,相等则提示重复,不同则可以继续后续操作。

4年前 评论
Sparkfly

第一步,用户打开表单,在后端生成一个 token 保存在 session 中,并渲染到表单

$token = rand(10000, 99999);
session('form_token',  $token);
<form>
    <input type='hidden' name='token' value='{{ $token }}' />
</form>

第二步,表单提交时,匹配 token 有效则执行,执行后清除 session 中的 token

$input_token = request()->input('token');
$token = session('form_token');

if($input_token == $token) {
     // todo save
     // clear session token
     session('form_token', null);
} else {
     return '重复提交表单';
}
4年前 评论
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class LimitFormRepeatSubmitMiddleware
{
    /**
     * 限制表单重复提交
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @param int $limit 限制时间(缓存时间)
     * @return mixed
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function handle(Request $request, Closure $next, $limit = 1)
    {
        // 如果存在表单, 根据 _token 判断
        if ($request->filled('_token')) {
            $token = $request->input('_token');
            $cache_key_form_token = self::class . '::' . $token;

            if (cache()->has($cache_key_form_token)) {
                if ($request->ajax() || $request->wantsJson() || $request->expectsJson()) {
                    return response()->json(['success' => false, 'code' => 500, 'message' => "请不要重复提交表单"]);
                } else {
                    return redirect()->back()->with('message', "请不要重复提交表单");
                }
            }

            cache([$cache_key_form_token => $token], $limit);
        }

        return $next($request);
    }
}
1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!