Rails CSRF Protection + Angular.js : protect_from_forgery는 POST에서 로그 아웃하도록합니다


129

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

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


이것이 HTTP 헤더 설정에 도움이되는지 확인하십시오. stackoverflow.com/questions/14183025/…
Mark Rajcok

답변:


276

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

18
클라이언트 측 코드를 수정할 필요가 없으므로이 기술이 마음에 듭니다.
Michelle Tilley

11
이 솔루션은 CSRF 보호의 유용성을 어떻게 유지합니까? 쿠키를 설정하면 표시된 사용자의 브라우저가 사이트 간 요청을 포함한 모든 후속 요청에서 해당 쿠키를 보냅니다. 악의적 인 요청을 보내는 악의적 인 타사 사이트를 설정할 수 있으며 사용자의 브라우저가 서버에 'XSRF-TOKEN'을 보냅니다. 이 솔루션은 CSRF 보호 기능을 완전히 끄는 데 큰 도움이됩니다.
Steven

9
Angular 문서에서 : "도메인에서 실행되는 JavaScript 만 쿠키를 읽을 수 있으므로 서버는 XHR이 도메인에서 실행되는 JavaScript에서 온 것임을 확신 할 수 있습니다." @StevenXu-타사 사이트는 쿠키를 어떻게 읽습니까?
Jimmy Baker

8
@JimmyBaker : 그렇습니다. 설명서를 검토했습니다. 접근 방식은 개념적으로 양호합니다. 쿠키의 설정을 유효성 검사와 혼동했지만 프레임 워크가 쿠키의 값을 기반으로 사용자 정의 헤더를 설정하고 있음을 인식하지 못했습니다!
Steven

5
form_authenticity_token은 Rails 4.2의 각 호출에서 새로운 값을 생성하므로 더 이상 작동하지 않는 것 같습니다.
Dave

78

기본 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의 답변 을 확인하십시오.이 답변 은 클라이언트가 아닌 서버의 모든 기반을 포괄합니다.


설명하겠습니다. 기본 문서는 일반 HTML이며 .erb가 아니므로 사용할 수 없습니다 <%= csrf_meta_tags %>. 나는 언급 protect_from_forgery만으로 충분하다고 생각했다 . 무엇을해야합니까? 기본 문서는 일반 HTML이어야합니다 (여기서는 내가 선택한 문서가 아닙니다).
Paul

3
protect_from_forgery"내 JavaScript 코드가 Ajax 요청 X-CSRF-Token을 할 때 현재 CSRF 토큰에 해당하는 헤더 를 보내겠다"고 말하는 것을 사용 합니다 . 이 토큰을 얻기 위해 Rails는 토큰을 DOM에 삽입하고 <%= csrf_meta_token %>Ajax 요청을 할 때마다 jQuery로 메타 태그의 내용을 가져옵니다 (기본 Rails 3 UJS 드라이버가이를 수행함). ERB를 사용하지 않는 경우 현재 토큰을 Rails에서 페이지 및 / 또는 JavaScript로 가져올 수있는 방법이 없으므로이 protect_from_forgery방식으로 사용할 수 없습니다 .
Michelle Tilley 2013

설명해 주셔서 감사합니다. 고전적인 서버 측 응용 프로그램에서 클라이언트 측은 csrf_meta_tags서버가 응답을 생성 할 때마다 이러한 태그가 이전 태그와 다를 때마다 수신한다고 생각했습니다 . 따라서이 태그는 각 요청마다 고유합니다. 문제는 애플리케이션이 어떻게 AJAX 요청에 대해 이러한 태그를 수신합니까 (각도없이)? jQuery POST 요청에 protect_from_forgery를 사용 했으며이 CSRF 토큰을 얻는 데 신경 쓰지 않았습니다. 어떻게?
Paul

1
레일즈 UJS 드라이버 사용 jQuery.ajaxPrefilter다음과 같이이 : github.com/indirect/jquery-rails/blob/c1eb6ae/vendor/assets/... 이 파일을 정독 할 수 있으며 필요없이 농구 레일을 통해 이동 모두가 거의 작동하도록 참조 그것에 대해 걱정하십시오.
Michelle Tilley 2013

@BrandonTilley 그것은 의미 만에이 작업을 수행 할 수 있도록 않을 것 putpost대신에 common? 로부터 레일 보안 가이드 :The solution to this is including a security token in non-GET requests
christianvuerings

29

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

# Gemfile
gem 'angular_rails_csrf'

angular_rails_csrf를 올바르게 사용하도록 응용 프로그램 컨트롤러 및 기타 csrf / 위조 관련 설정을 구성하는 방법에 대한 아이디어가 있습니까?
벤 휠러

이 의견을 제출할 때 angular_rails_csrfgem은 Rails 5에서 작동하지 않습니다. 그러나 CSRF 메타 태그의 값으로 Angular 요청 헤더를 구성하면 작동합니다!
bideowego

Rails 5를 지원하는 새로운 gem 버전이 출시되었습니다.
jsanders

4

모든 이전 답변을 병합하고 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')

1
$injector직접 주사 하는 대신 주사 하는 이유는 무엇 $http입니까?
whitehat101

이것은 작동하지만 요청이 이미 반복되었는지 확인하는 것만 추가한다고 생각합니다. 반복 될 때 우리는 영원히 반복되므로 다시 전송하지 않습니다.
duleorlovic

1

나는 다른 답을 보았고 그들이 훌륭하고 잘 생각했다고 생각했습니다. 나는 더 간단한 해결책이라고 생각한 것과 함께 레일 응용 프로그램을 작동 시켜서 공유 할 것이라고 생각했습니다. 내 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

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


6
이것은 레일 '세션'을 사용하려고 할 때 위조 테스트에 실패하면 nil로 설정되므로 문제가 발생할 수 있습니다. 클라이언트 측에서 csrf 토큰을 보내지 않기 때문에 항상 그렇습니다.
hajpoj 2016 년

그러나 Rails 세션을 사용하지 않는다면 모든 것이 좋습니다. 감사합니다! 나는 이것에 대한 가장 깨끗한 해결책을 찾기 위해 고심하고 있습니다.
Morgan

1

내 응용 프로그램에서 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

1

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

ㅏ. 내 관점에서, 나는 $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 = {}

더 이상 할 일이 없습니다.


0
 angular
  .module('corsInterceptor', ['ngCookies'])
  .factory(
    'corsInterceptor',
    function ($cookies) {
      return {
        request: function(config) {
          config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
          return config;
        }
      };
    }
  );

그것은 angularjs 측에서 일하고 있습니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.