Ruby on Rails:表单提交时出现“拒绝更改”错误。

4

ruby 3.0.1 rails 6.1.2 'devise','〜> 4.7','>= 4.7.3'

我遇到了一个非常不寻常的情况。我正在将一个rails安装从一个服务器迁移到另一个服务器。我相信我已经完成了95%的工作,只是恢复了生产数据库。

但是,包括用户注册和登录在内的任何涉及表单提交的操作都会给我带来错误页面:

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

服务器日志提供了更有帮助的信息:
Completed 422 Unprocessable Entity in 2ms (Allocations: 433)
FATAL -- ActionController::InvalidAuthenticityToken

这让我感到困惑。因为我已经重新生成了master.key和credentials.yml.enc,并通过RAILS_MASTER_KEY环境变量公开了master.key的内容。这意味着表单包含适当的,以防止跨站点脚本攻击。
我认为这与会话无关,因为这甚至影响到用户注册。我正在使用Devise进行身份验证。
但是...现在我遇到了难题。从这里没有出路了。有人知道问题出在哪里吗?
更新1
添加skip_before_action :verify_authenticity_token确实允许我跳过这个问题。但我不满意这个解决方案。
更新2
我有这些元标记。
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

你清除了浏览器的cookies并重试了吗? - Rockwell Rice
我刚刚尝试过了。然而问题仍然存在。如果那是解决方案,我会感到惊讶的。 - devleo
你正在使用 devise 吗? - Lam Phan
是的,如果这有助于其他人,我会在我的帖子中添加这个。 - devleo
这可能会有所帮助:https://gist.github.com/db0sch/19c321cbc727917bc0e12849a7565af9,我看到了一个重要的提示:“..在devise.rb中遇到了问题。我只是取消注释了secret_key = ...这一行,只是为了运行命令重新生成凭据文件,然后再次将该行注释掉。” - Lam Phan
显示剩余3条评论
2个回答

4

很不好意思,但是nginx没有正确配置。当我在rails中达到瓶颈时,我终于开始问还有什么其他问题。

问题在于,在内部,rails正在使用http来访问各种端点。我的nginx重定向块看起来像这样:

 server {
  listen 80 default_server;

  server_name _;

  return 301 https://$host$request_uri;
}

这意味着每当 http 访问内部端点时,它会被重定向到 https,并返回 301 ... 这总是一个 GET 请求。一旦我在 production.rb 中使用了 config.force_ssl = true,我只需要重新配置我的 nginx,以支持它。

这是可用的配置:

location / {
  proxy_set_header Host $http_host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto https;
  proxy_redirect off;
  proxy_pass http://app:3000;
}

因为请求被发送为GET方法,导致无法处理实体(unprocessable entity),所以看起来像是认证令牌(auth token)的问题。可能在请求到达后,令牌为空或其他原因,即使在浏览器检查中我能看到它。实际上只是HTTP动词不正确。

顺便说一下,代理到本地端口并不理想(这也是我以前运行的方式 xD)。Apache 有 mod_passenger,非常适合 Rails,我认为他们也支持 Nginx。https://www.phusionpassenger.com/library/config/nginx/intro.html 最好使用与 Nginx 集成的真正应用程序服务器。 - nlta
感谢您编写解决方案,我在将Rails升级到7.0.1时遇到了同样的问题,而我错过了在我的nginx配置中添加这一行proxy_set_header X-Forwarded-Proto https; - widjajayd

1
您的表单未随请求发送X-CSRF-TOKEN头。该头是一项安全功能,您不开启它感到不安是正确的。它是防止evil.com将表单发送到yourwebsite.co的部分内容。
Rails的默认设置<%= forms_* %>会自动处理这个问题,因此您可能使用了自定义内容。
如果您正在从JavaScript中发送这些请求,则可以使用以下方法:
let token = document.querySelector("meta[name='csrf-token']").content
fetch(url, {
    method,
    headers: {
        'X-CSRF-Token': auth_token, // <---- this
        'X-Requested-With': 'XMLHttpRequest',
        ...
    },
...

也有可能是因为您没有在application.html.erb文件中引入它,导致表单找不到它。

<!DOCTYPE html>
<html>
<head>
  <%= csrf_meta_tags %> <!-- you need this! -->
...

看起来我有两个类似的标签。<%= csrf_meta_tags %><%= csp_meta_tag %>我还注意到每个表单都失败了,即使与身份验证无关。正在更新我的帖子。我非常强烈地感觉这甚至与Rails无关。令牌已经被正确发送。我接下来要调查的是我的Nginx配置。谢谢您抽出时间回复。 - devleo
请注意,您应该同时启用CSP和CSRF。CSP是内容安全策略,它控制内容可以来自哪里(例如,我应该从evil.com加载脚本还是阻止它),而CSRF有助于防止其他网站上的js向您的域发出请求。 - nlta
感谢您详细的回答。尽管它并没有成为我需要的解决方案,但我仍然感谢您花费的时间。我觉得我终于明白了那个标记存在的目的。 - devleo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接