Rails 7生产环境部分ActiveStorage图片无法删除:CSRF令牌验证失败
Rails 7图片删除部分触发CSRF错误问题排查
问题描述
在Rails 7应用中,Product模型与photos为has_many关联,在photos#edit页面展示图片并提供单图删除按钮,大部分图片可正常删除,但部分图片删除时会抛出CSRF验证错误。
相关代码片段
图片删除按钮ERB代码
<% product.photos.each do |photo| %> <div class="inline-block"> <%= image_tag(photo, width: 25) %> <%= button_to '<i class="fas fa-times-circle fa-xs" style="margin-left: -10px; position: relative; top: -10px"></i>'.html_safe, product_photo_path(product, photo), method: :delete, class: "btn-invisible", data: { confirm: 'Are you sure?' } %> </div> <% end %>
渲染后的HTML
<img width="25" src="https://lgw-2023.herokuapp.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBWWc9IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6a79941d68ebd75d1a09941f6081eba67e7fd511/Recess%20Kit.jpg" /> <form class="button_to" method="post" action="/products/113/photos/760"><input type="hidden" name="_method" value="delete" autocomplete="off" /><button class="btn-invisible" data-confirm="Are you sure?" type="submit"><i class="fas fa-times-circle fa-xs" style="margin-left: -10px; position: relative; top: -10px"></i></button><input type="hidden" name="authenticity_token" value="K7hCXTJyZPcRi528zAhFtPnfa1bjYg-WphGGb97lnSOh6nNw8ZPFWHhhvUy-CZ1MBYagtxh9Hqs78IsNlTrPaw" autocomplete="off" /></form>
Photos控制器代码
class PhotosController < ApplicationController def destroy @photo = ActiveStorage::Attachment.find(params[:id]) product = Product.find(@photo.record_id) @photo.purge redirect_to edit_product_path(product) end end
Heroku错误日志
2023-11-22T03:42:36.183342+00:00 app[web.1]: I, [2023-11-22T03:42:36.183279 #2] INFO -- : [bef85162-98ad-485c-b57c-6b36af83a64a] Started DELETE "/products/113" for 70.181.107.14 at 2023-11-22 03:42:36 +0000 2023-11-22T03:42:36.184058+00:00 app[web.1]: I, [2023-11-22T03:42:36.184020 #2] INFO -- : [bef85162-98ad-485c-b57c-6b36af83a64a] Processing by ProductsController#destroy as HTML 2023-11-22T03:42:36.184099+00:00 app[web.1]: I, [2023-11-22T03:42:36.184072 #2] INFO -- : [bef85162-98ad-485c-b57c-6b36af83a64a] Parameters: {"authenticity_token"=>"[FILTERED]", "product"=>{"name"=>"V11 Animal", "product_name_id"=>"14", "sku"=>"", "MSRP"=>"629.99", "lowest_price"=>"629.99", "model"=>"V11", "color"=>"", "dimensions"=>"", "voltage"=>"", "voltage_category"=>"N/A", "photos"=>[""], "historical_models"=>"", "description"=>"", "active"=>"1"}, "id"=>"113"} 2023-11-22T03:42:36.184545+00:00 app[web.1]: W, [2023-11-22T03:42:36.184513 #2] WARN -- : [bef85162-98ad-485c-b57c-6b36af83a64a] Can't verify CSRF token authenticity. 2023-11-22T03:42:36.184743+00:00 app[web.1]: I, [2023-11-22T03:42:36.184722 #2] INFO -- : [bef85162-98ad-485c-b57c-6b36af83a64a] Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms | Allocations: 340) 2023-11-22T03:42:36.185320+00:00 app[web.1]: F, [2023-11-22T03:42:36.185280 #2] FATAL -- : [bef85162-98ad-485c-b57c-6b36af83a64a] 2023-11-22T03:42:36.185321+00:00 app[web.1]: [bef85162-98ad-485c-b57c-6b36af83a64a] ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.):
原因分析
从错误日志可以明确看到核心问题:
- 触发CSRF错误的请求是删除产品的
DELETE /products/113,而非预期的删除图片的DELETE /products/113/photos/760。 - 请求参数包含完整的产品表单数据,说明点击图片删除按钮时,实际触发了页面上的产品表单提交(比如产品删除表单或编辑表单)。
导致这个问题的直接原因是:
- 图片删除按钮使用了
margin-left: -10px和position: relative的样式,使得按钮的点击区域超出了自身的<div>容器,覆盖到了页面上的其他表单元素。 - 用户点击该按钮时,浏览器实际响应的是下层被覆盖的表单提交,而该表单的CSRF token可能已过期,或因表单状态异常导致验证失败。
解决方案
1. 修复按钮定位问题
调整布局,确保图片删除按钮的点击区域完全在自身容器内,不会覆盖其他表单。推荐使用相对定位容器配合绝对定位按钮的方式:
<% product.photos.each do |photo| %> <div class="inline-block relative"> <%= image_tag(photo, width: 25) %> <%= button_to '<i class="fas fa-times-circle fa-xs"></i>'.html_safe, product_photo_path(product, photo), method: :delete, class: "btn-invisible absolute -top-2 -left-2", data: { confirm: 'Are you sure?' } %> </div> <% end %>
这里给外层容器添加relative作为定位上下文,按钮用absolute定位到图片左上角,避免点击区域溢出。
2. 验证表单触发目标
使用浏览器开发者工具检查点击按钮时实际触发的表单元素,确认定位问题是否解决。可以给图片删除表单添加独特class,方便排查:
<%= button_to ..., class: "btn-invisible absolute -top-2 -left-2 photo-delete-btn" %>
3. 检查页面其他表单状态
确认edit_product_path页面上的产品删除/编辑表单的CSRF token有效性,若页面加载时间过长,可刷新页面重新获取有效token。
内容的提问来源于stack exchange,提问作者Liz
相关产品推荐
相关产品推荐

