[ruby-on-rails] Rails CSRF Protection + Angular.js : protect_from_forgery는 POST에서 로그 아웃하도록합니다

protect_from_forgery옵션이 application_controller에 언급되어 있으면 로그인하고 GET 요청을 수행 할 수 있지만 처음 POST 요청에서 Rails가 세션을 재설정하여 로그 아웃합니다.

나는되어 protect_from_forgery일시적으로 해제 옵션을하지만, Angular.js와 함께 사용하고 싶습니다. 그렇게 할 방법이 있습니까?



답변

DOM에서 CSRF 값을 읽는 것이 좋은 해결책은 아니라고 생각합니다.

다음은 angularJS 공식 웹 사이트 http://docs.angularjs.org/api/ng.$http 의 문서 양식입니다 .

도메인에서 실행되는 JavaScript만이 쿠키를 읽을 수 있으므로 서버는 XHR이 도메인에서 실행되는 JavaScript에서 온 것임을 확신 할 수 있습니다.

이를 활용하려면 (CSRF Protection) 서버가 첫 번째 HTTP GET 요청에서 XSRF-TOKEN이라는 JavaScript 읽기 가능 세션 쿠키에 토큰을 설정해야합니다. 후속 비 GET 요청에서 서버는 쿠키가 X-XSRF-TOKEN HTTP 헤더와 일치하는지 확인할 수 있습니다.

다음은 해당 지침을 기반으로 한 솔루션입니다.

먼저 쿠키를 설정하십시오.

# app/controllers/application_controller.rb

# Turn on request forgery protection
protect_from_forgery

after_action :set_csrf_cookie

def set_csrf_cookie
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end

그런 다음 GET이 아닌 모든 요청에서 토큰을 확인해야합니다.
Rails는 이미 비슷한 방법으로 구축되었으므로 단순히이를 재정 의하여 로직을 추가 할 수 있습니다.

# app/controllers/application_controller.rb

protected
  
  # In Rails 4.2 and above
  def verified_request?
    super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
  end

  # In Rails 4.1 and below
  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end


답변

기본 Rails CSRF 보호 ( <%= csrf_meta_tags %>)를 사용하는 경우 다음 과 같이 Angular 모듈을 구성 할 수 있습니다.

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
]

또는 CoffeeScript를 사용하지 않는 경우 (what !?) :

myAngularApp.config([
  "$httpProvider", function($httpProvider) {
    $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
  }
]);

원하는 경우 GET 이외의 요청에 대해서만 다음과 같은 방법으로 헤더를 보낼 수 있습니다.

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  csrfToken = $('meta[name=csrf-token]').attr('content')
  $httpProvider.defaults.headers.post['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.put['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.patch['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.delete['X-CSRF-Token'] = csrfToken
]

또한 HungYuHei의 답변 을 확인하십시오.이 답변 은 클라이언트가 아닌 서버의 모든 기반을 포괄합니다.


답변

angular_rails_csrf의 보석은 자동으로 설명 된 패턴에 대한 지원을 추가 HungYuHei의 대답은 모든 컨트롤러 :

# Gemfile
gem 'angular_rails_csrf'


답변

모든 이전 답변을 병합하고 Devise인증 gem을 사용하고있는 답변 입니다.

우선, 보석을 추가하십시오 :

gem 'angular_rails_csrf'

다음 rescue_from으로 application_controller.rb 에 블록을 추가하십시오 :

protect_from_forgery with: :exception

rescue_from ActionController::InvalidAuthenticityToken do |exception|
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  render text: 'Invalid authenticity token', status: :unprocessable_entity
end

마지막으로 인터셉터 모듈을 각도 앱에 추가하십시오.

# coffee script
app.factory 'csrfInterceptor', ['$q', '$injector', ($q, $injector) ->
  responseError: (rejection) ->
    if rejection.status == 422 && rejection.data == 'Invalid authenticity token'
        deferred = $q.defer()

        successCallback = (resp) ->
          deferred.resolve(resp)
        errorCallback = (resp) ->
          deferred.reject(resp)

        $http = $http || $injector.get('$http')
        $http(rejection.config).then(successCallback, errorCallback)
        return deferred.promise

    $q.reject(rejection)
]

app.config ($httpProvider) ->
  $httpProvider.interceptors.unshift('csrfInterceptor')


답변

나는 다른 답을 보았고 그들이 훌륭하고 잘 생각했다고 생각했습니다. 나는 더 간단한 해결책이라고 생각한 것과 함께 레일 응용 프로그램을 작동 시켜서 공유 할 것이라고 생각했습니다. 내 Rails 앱은 기본적으로 제공됩니다.

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
end

나는 주석을 읽었고 그것이 각도를 사용하고 csrf 오류를 피하고 싶은 것처럼 보입니다. 나는 이것을 이것으로 바꿨다.

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :null_session
end

그리고 지금 작동합니다! 왜 이것이 작동하지 않는지 알 수 없지만 다른 포스터의 통찰력을 듣고 싶습니다.


답변

내 응용 프로그램에서 HungYuHei의 답변 내용을 사용했습니다. 그러나 인증을 위해 Devise를 사용하고 일부는 내 응용 프로그램에서 얻은 기본값 때문에 몇 가지 추가 문제를 처리하고 있음을 발견했습니다.

protect_from_forgery with: :exception

관련 스택 오버플로 질문과 거기대한 답변에 주목하고 다양한 고려 사항을 요약 한 훨씬 더 자세한 블로그 게시물 을 작성했습니다 . 여기에 해당 솔루션의 일부는 애플리케이션 컨트롤러에 있습니다.

  protect_from_forgery with: :exception

  after_filter :set_csrf_cookie_for_ng

  def set_csrf_cookie_for_ng
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  end

  rescue_from ActionController::InvalidAuthenticityToken do |exception|
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
    render :error => 'Invalid authenticity token', {:status => :unprocessable_entity} 
  end

protected
  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end


답변

나는 이것에 대한 매우 빠른 해킹을 발견했다. 내가해야 할 일은 다음과 같습니다.

ㅏ. 내 관점에서, 나는 $scope토큰을 포함 하는 변수를 초기화 하거나, 양식 전에 말하거나 심지어 컨트롤러 초기화에 더 잘합시다.

<div ng-controller="MyCtrl" ng-init="authenticity_token = '<%= form_authenticity_token %>'">

비. AngularJS 컨트롤러에서 새 항목을 저장하기 전에 해시에 토큰을 추가합니다.

$scope.addEntry = ->
    $scope.newEntry.authenticity_token = $scope.authenticity_token
    entry = Entry.save($scope.newEntry)
    $scope.entries.push(entry)
    $scope.newEntry = {}

더 이상 할 일이 없습니다.