登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
轻量养虾,开箱即用!低 Token + 稳定算力,Gitee & 模力方舟联合出品的 PocketClaw 正式开售!点击了解详情
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
3
Star
47
Fork
22
DreamCoders
/
CoderGuide
代码
Issues
1169
Pull Requests
0
Wiki
统计
流水线
服务
JavaDoc
PHPDoc
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
开发画像分析
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
怎么预防按钮的重复点击?
待办的
#IAG9PS
陌生人
拥有者
创建于
2024-07-29 16:07
<p>先看看在那些场景会导致重复请求: </p><ol><li>手速快,不小心双击操作按钮。 </li><li>很小心的点击了一次按钮,因为请求响应比较慢,页面没有任何提示,怀疑上次点击没生效,再次点击操作按钮。 </li><li>很小心的点击了一次按钮,因为请求响应比较慢,页面没有任何提示,刷新页面,再次点击操作按钮。</li></ol><h4>前端方案</h4><p>我们可以对症下药: </p><ol><li>控制按钮,在短时间内被多次点击,第一次以后的点击无效。 </li><li>控制按钮,在点击按钮触发的请求响应之前,再次点击无效。 </li><li>配置特殊的URL,然后控制这些URL请求的最小时间间隔。如果再次请求跟前一次请求间隔很小,弹窗二次提示,是否继续操作。</li></ol><h4>防止无意识重复点击按钮</h4><p>给按钮添加控制,在<code>control</code> 毫秒内,第一次点击事件之后的点击事件不执行。</p><pre><code class="language-text"><template> <button @click="handleClick"></button> </templage> <script> export default { methods: { handleClick(event) { if (this.disabled) return; if (this.notAllowed) return; // 点击完多少秒不能继续点 this.notAllowed = true; setTimeout(()=>{ this.notAllowed = false; }, this.control) this.$emit('click', event, this); } } } </script></code></pre><p>当然时间间隔可以设置,默认为300毫秒。我们无意识的重复点击一般在300毫秒以内。</p><h4>按钮点击立马禁用,等响应回来才能继续点击</h4><p>触发点击的button实例传入fetch配置,代码如下:</p><pre><code class="language-js">doQuery: function (button) { this.FesApi.fetch(`generalcard/query`, { sub_card_type: this.query.sub_card_type, code_type: this.query.code_type, title: this.query.title, card_id: this.query.card_id, page_info: { pageSize: this.paginationOption.page_info.pageSize, currentPage: this.paginationOption.page_info.currentPage } }, { //看这里,加上下面一行代码就行。。so easy button: button }).then(rst => { // 成功处理 }); } </code></pre><p>在fetch函数内部,设置button的<code>disabled=true</code>,当响应回来时,设置<code>disabled=false</code>代码如下:</p><pre><code class="language-js">const action = function (url, data, option) { // 如果传了button if (option.button) { option.button.currentDisabled = true; } // 记录日志 const log = requsetLog.creatLog(url, data); return param(url, data, option) .then(success, fail) .then((response) => { requsetLog.changeLogStatus(log, 'success'); if (option && option.button) { option.button.currentDisabled = false; } return response; }) .catch((error) => { requsetLog.changeLogStatus(log, 'fail'); if (option && option.button) { option.button.currentDisabled = false; } error.message && window.Toast.error(error.message); throw error; }); }; </code></pre><h4>从根本入手,一招击杀</h4><p>当页面刷新,页面状态重置,此时再次点击按钮,会判定为初次点击,而且按钮状态恢复可点击。我们可以设置哪些请求地址是重要的,它们请求间隔不能过小。如果过小,页面弹出覆层询问用户时候继续执行。 </p><p>设置代码如下:</p><pre><code class="language-js">this.FesApi.setImportant({ 'generalcard/action': { control: 10000, message: '您在十秒内重复发起手工清算操作,是否继续?' } }) </code></pre><p>而实现代码如下:</p><pre><code class="language-js">api.fetch = function (url, data, option) { if (requsetLog.importantApi[url]) { const logs = requsetLog.getLogByURL(url, data); if (logs.length > 0) { const compareLog = logs[logs.length - 1]; if (compareLog.status === 'compare') { requsetLog.creatLog(url, data, 'notAllowed'); return { then: () => {} }; } const importantApiOption = requsetLog.importantApi[url]; const control = importantApiOption.control || 10000; const message = importantApiOption.message || util.format('fesMessages.importInterfaceTip', { s: control / 1000 }); if (new Date().getTime() - compareLog.timestamp < control) { const oldStatus = compareLog.status; requsetLog.changeLogStatus(compareLog, 'compare'); return new Promise(((resolve, reject) => { window.Message.confirm(util.format('fesMessages.tip'), message).then((index) => { if (compareLog.status === 'compare') { requsetLog.changeLogStatus(compareLog, oldStatus); } if (index === 0) { resolve(action(url, data, option)); } else { reject(new Error('不允许相同操作间隔过小')); } }); })); } return action(url, data, option); } return action(url, data, option); } return action(url, data, option); }; </code></pre><p>攻击者可以绕过正常流程,模拟发起多次请求,所以仅仅在前端页面做好预防重复请求工作是不够的。后台接口需要设计得更健壮,具有幂等性。</p>
<p>先看看在那些场景会导致重复请求: </p><ol><li>手速快,不小心双击操作按钮。 </li><li>很小心的点击了一次按钮,因为请求响应比较慢,页面没有任何提示,怀疑上次点击没生效,再次点击操作按钮。 </li><li>很小心的点击了一次按钮,因为请求响应比较慢,页面没有任何提示,刷新页面,再次点击操作按钮。</li></ol><h4>前端方案</h4><p>我们可以对症下药: </p><ol><li>控制按钮,在短时间内被多次点击,第一次以后的点击无效。 </li><li>控制按钮,在点击按钮触发的请求响应之前,再次点击无效。 </li><li>配置特殊的URL,然后控制这些URL请求的最小时间间隔。如果再次请求跟前一次请求间隔很小,弹窗二次提示,是否继续操作。</li></ol><h4>防止无意识重复点击按钮</h4><p>给按钮添加控制,在<code>control</code> 毫秒内,第一次点击事件之后的点击事件不执行。</p><pre><code class="language-text"><template> <button @click="handleClick"></button> </templage> <script> export default { methods: { handleClick(event) { if (this.disabled) return; if (this.notAllowed) return; // 点击完多少秒不能继续点 this.notAllowed = true; setTimeout(()=>{ this.notAllowed = false; }, this.control) this.$emit('click', event, this); } } } </script></code></pre><p>当然时间间隔可以设置,默认为300毫秒。我们无意识的重复点击一般在300毫秒以内。</p><h4>按钮点击立马禁用,等响应回来才能继续点击</h4><p>触发点击的button实例传入fetch配置,代码如下:</p><pre><code class="language-js">doQuery: function (button) { this.FesApi.fetch(`generalcard/query`, { sub_card_type: this.query.sub_card_type, code_type: this.query.code_type, title: this.query.title, card_id: this.query.card_id, page_info: { pageSize: this.paginationOption.page_info.pageSize, currentPage: this.paginationOption.page_info.currentPage } }, { //看这里,加上下面一行代码就行。。so easy button: button }).then(rst => { // 成功处理 }); } </code></pre><p>在fetch函数内部,设置button的<code>disabled=true</code>,当响应回来时,设置<code>disabled=false</code>代码如下:</p><pre><code class="language-js">const action = function (url, data, option) { // 如果传了button if (option.button) { option.button.currentDisabled = true; } // 记录日志 const log = requsetLog.creatLog(url, data); return param(url, data, option) .then(success, fail) .then((response) => { requsetLog.changeLogStatus(log, 'success'); if (option && option.button) { option.button.currentDisabled = false; } return response; }) .catch((error) => { requsetLog.changeLogStatus(log, 'fail'); if (option && option.button) { option.button.currentDisabled = false; } error.message && window.Toast.error(error.message); throw error; }); }; </code></pre><h4>从根本入手,一招击杀</h4><p>当页面刷新,页面状态重置,此时再次点击按钮,会判定为初次点击,而且按钮状态恢复可点击。我们可以设置哪些请求地址是重要的,它们请求间隔不能过小。如果过小,页面弹出覆层询问用户时候继续执行。 </p><p>设置代码如下:</p><pre><code class="language-js">this.FesApi.setImportant({ 'generalcard/action': { control: 10000, message: '您在十秒内重复发起手工清算操作,是否继续?' } }) </code></pre><p>而实现代码如下:</p><pre><code class="language-js">api.fetch = function (url, data, option) { if (requsetLog.importantApi[url]) { const logs = requsetLog.getLogByURL(url, data); if (logs.length > 0) { const compareLog = logs[logs.length - 1]; if (compareLog.status === 'compare') { requsetLog.creatLog(url, data, 'notAllowed'); return { then: () => {} }; } const importantApiOption = requsetLog.importantApi[url]; const control = importantApiOption.control || 10000; const message = importantApiOption.message || util.format('fesMessages.importInterfaceTip', { s: control / 1000 }); if (new Date().getTime() - compareLog.timestamp < control) { const oldStatus = compareLog.status; requsetLog.changeLogStatus(compareLog, 'compare'); return new Promise(((resolve, reject) => { window.Message.confirm(util.format('fesMessages.tip'), message).then((index) => { if (compareLog.status === 'compare') { requsetLog.changeLogStatus(compareLog, oldStatus); } if (index === 0) { resolve(action(url, data, option)); } else { reject(new Error('不允许相同操作间隔过小')); } }); })); } return action(url, data, option); } return action(url, data, option); } return action(url, data, option); }; </code></pre><p>攻击者可以绕过正常流程,模拟发起多次请求,所以仅仅在前端页面做好预防重复请求工作是不够的。后台接口需要设计得更健壮,具有幂等性。</p>
评论 (
0
)
登录
后才可以发表评论
状态
待办的
待办的
进行中
已完成
已关闭
负责人
未设置
标签
Html/JS/CSS
未设置
标签管理
里程碑
未关联里程碑
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
分支 (
-
)
标签 (
-
)
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(1)
1
https://gitee.com/DreamCoders/CoderGuide.git
git@gitee.com:DreamCoders/CoderGuide.git
DreamCoders
CoderGuide
CoderGuide
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册