升级至Rails 7后出现CSRF错误,成因是什么?
解决Rails 7中ActiveStorage直传偶发CSRF令牌验证失败问题
一、关于CSRF令牌与部署/重启的关系
- Rails默认的CSRF令牌绑定到用户session,只要
secret_key_base未变更,部署或应用重启不会导致令牌失效。session默认存在cookie中,令牌不会随应用重启改变。即便启用config.action_controller.per_form_csrf_tokens = true(每个表单生成独立令牌),令牌也不会因部署重启失效,仅表单间不通用。
二、直传JS代码的潜在问题
你的代码实现了directUploadWillStoreFileWithXHR钩子,但未确保CSRF令牌被正确添加到XHR请求头。Rails 7中ActiveStorage默认会自动添加CSRF头,但自定义该钩子时,需手动确认头信息设置:
修改directUploadWillStoreFileWithXHR方法,手动添加CSRF令牌头:
@directUploadWillStoreFileWithXHR = (request) -> that = this @xhrRequest = request # 手动添加CSRF令牌请求头 request.setRequestHeader('X-CSRF-Token', document.querySelector('meta[name="csrf-token"]').content) request.upload.addEventListener "progress", (event) -> that.directUploadDidProgress(event)
三、大文件上传偶发失败的核心原因
大文件上传耗时久,以下场景会触发令牌验证失败:
- 页面闲置过久:用户打开上传页面后长时间未操作,后续发起上传时,页面中的CSRF令牌可能已过期(或因用户在其他标签页登录/退出,导致令牌与当前session不匹配)。
- Turbo机制影响:Rails 7默认启用Turbo,Turbo更新页面时会自动刷新meta标签中的CSRF令牌。若用户上传过程中页面被Turbo更新,上传请求仍使用旧令牌,就会触发验证失败。
四、额外优化方案
- 上传前刷新CSRF令牌:在
startUpload方法中,先获取最新令牌,确保上传请求使用当前有效令牌:
@startUpload = (file) -> input = document.querySelector('input[type=file]') # 刷新CSRF令牌 document.querySelector('meta[name="csrf-token"]').content = Rails.csrfToken() App.Uploader.inProgress = new App.Uploader(file, input.dataset.directUploadUrl, this) App.Uploader.inProgress.upload() # 后续原有代码保持不变...
- CSRF错误重试机制:在上传失败回调中检测是否为CSRF错误,刷新令牌后重新发起上传:
@upload = -> @directUpload.create (error, blob) => if error # 判断是否为CSRF令牌错误 if error.message.includes?('InvalidAuthenticityToken') # 刷新令牌并重试上传 document.querySelector('meta[name="csrf-token"]').content = Rails.csrfToken() @directUpload.create(arguments.callee) else console.log error $('#uploading_done').text('There was an issue uploading your file.').show().css('background', '#D4524B') $('#uploading').hide() else $('input#podcast_file').val(blob.signed_id).closest('form').submitWithRails()
内容的提问来源于stack exchange,提问作者John Pollard
相关产品推荐
相关产品推荐

