Рельсы - InvalidAuthenticityToken для запросов json/xml
По какой-то причине я получаю InvalidAuthenticityToken при отправке запросов в мое приложение при использовании json или xml. Насколько я понимаю, rails должен требовать токен подлинности только для запросов html или js, и поэтому я не должен сталкиваться с этой ошибкой. Единственное решение, которое я нашел до сих пор, это отключение protect_from_forgery для любого действия, к которому я хотел бы получить доступ через API, но это не идеально по очевидным причинам. Мысли?
def create
respond_to do |format|
format.html
format.json{
render :json => Object.create(:user => @current_user, :foo => params[:foo], :bar => params[:bar])
}
format.xml{
render :xml => Object.create(:user => @current_user, :foo => params[:foo], :bar => params[:bar])
}
end
end
и это то, что я получаю в журналах всякий раз, когда я передаю запрос к действию:
Processing FooController#create to json (for 127.0.0.1 at 2009-08-07 11:52:33) [POST]
Parameters: {"foo"=>"1", "api_key"=>"44a895ca30e95a3206f961fcd56011d364dff78e", "bar"=>"202"}
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
thin (1.2.2) lib/thin/connection.rb:76:in `pre_process'
thin (1.2.2) lib/thin/connection.rb:74:in `catch'
thin (1.2.2) lib/thin/connection.rb:74:in `pre_process'
thin (1.2.2) lib/thin/connection.rb:57:in `process'
thin (1.2.2) lib/thin/connection.rb:42:in `receive_data'
eventmachine (0.12.8) lib/eventmachine.rb:242:in `run_machine'
eventmachine (0.12.8) lib/eventmachine.rb:242:in `run'
thin (1.2.2) lib/thin/backends/base.rb:57:in `start'
thin (1.2.2) lib/thin/server.rb:156:in `start'
thin (1.2.2) lib/thin/controllers/controller.rb:80:in `start'
thin (1.2.2) lib/thin/runner.rb:174:in `send'
thin (1.2.2) lib/thin/runner.rb:174:in `run_command'
thin (1.2.2) lib/thin/runner.rb:140:in `run!'
thin (1.2.2) bin/thin:6
/opt/local/bin/thin:19:in `load'
/opt/local/bin/thin:19
7 ответов
С protect_from_forgery
При включенном Rails требуется токен аутентификации для любых не-GET запросов. Rails автоматически включает токен аутентификации в формы, созданные с помощью помощников форм, или ссылки, созданные с помощью помощников AJAX, поэтому в обычных случаях вам не придется об этом думать.
Если вы не используете встроенную форму Rails или помощников AJAX (возможно, вы используете ненавязчивый JS или каркас JS MVC), вам придется самостоятельно установить токен на стороне клиента и отправить его вместе с вашим данные при отправке запроса POST. Вы бы поместили такую строку в <head>
вашего макета:
<%= javascript_tag "window._token = '#{form_authenticity_token}'" %>
Тогда ваша AJAX-функция отправит токен с другими вашими данными (пример с jQuery):
$.post(url, {
id: theId,
authenticity_token: window._token
});
Это то же самое, что и ответ @user1756254, но в Rails 5 вам нужно использовать немного другой синтаксис:
protect_from_forgery unless: -> { request.format.json? }
Источник: http://api.rubyonrails.org/v5.0/classes/ActionController/RequestForgeryProtection.html
У меня была похожая ситуация, и проблема была в том, что я не отправлял через правильные заголовки типов контента - я запрашивал text/json
и я должен был просить application/json
,
я использовал curl
следующее для проверки моего приложения (при необходимости измените):
curl -H "Content-Type: application/json" -d '{"person": {"last_name": "Lambie","first_name": "Matthew"}}' -X POST http://localhost:3000/people.json -i
Или вы можете сохранить JSON в локальный файл и вызвать curl
как это:
curl -H "Content-Type: application/json" -v -d @person.json -X POST http://localhost:3000/people.json -i
Когда я изменил заголовки типа контента на право application/json
все мои проблемы исчезли, и мне больше не нужно было отключать защиту от подделки.
Другой способ - избегать verify_authenticity_token, используя skip_before_filter в вашем приложении Rails:
skip_before_action :verify_authenticity_token, only: [:action1, :action2]
Это позволит curl делать свою работу.
Добавляя ответ на andymism, вы можете использовать его, чтобы применить включение TOKEN по умолчанию в каждый запрос POST:
$(document).ajaxSend(function(event, request, settings) {
if ( settings.type == 'POST' || settings.type == 'post') {
settings.data = (settings.data ? settings.data + "&" : "")
+ "authenticity_token=" + encodeURIComponent( window._token );
}
});
Пока JavaScript находится на веб-сайте, обслуживаемом Rails (например, фрагмент JS или приложение React, управляемое через webpacker), вы можете использовать значение в
csrf_meta_tags
включены по умолчанию.
В
application.html.erb
:
<html>
<head>
...
<%= csrf_meta_tags %>
...
Поэтому в HTML вашего сайта:
<html>
<head>
<meta name="csrf-token" content="XZY">
Возьмите жетон из
content
свойство и использовать его в запросе:
const token = document.head.querySelector('meta[name="csrf-token"]').content
const response = await fetch("/entities/1", {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ authenticity_token: token, entity: { name: "new name" } })
});
Это похоже на ответ @andrewle, но в дополнительном токене нет необходимости.
Чтобы добавить к ответу Фернандо, если ваш контроллер отвечает и на json, и на html, вы можете использовать:
skip_before_filter :verify_authenticity_token, if: :json_request?