레일, 인증 설계, CSRF 문제
나는 레일즈를 이용하여 싱글 페이지 애플리케이션을 하고 있다.로그인 및 로그아웃 시 Ajax를 사용하여 Gand 컨트롤러를 호출합니다.문제가 발생하고 있는 것은 1) 사인아웃을 해도 다시 사인할 수 없다는 것입니다.
사인아웃 시 리셋되는 CSRF 토큰과 관련된 것이라고 생각합니다(단, 1페이지이므로 이전 CSRF 토큰이 xhr 요청으로 전송되어 세션이 리셋됩니다).
좀 더 구체적으로 말하면 다음과 같은 워크플로우입니다.
- 사인인
- 로그아웃
- 201번입니다., 인쇄하다, 인쇄하다
WARNING: Can't verify CSRF token authenticity★★★★★★★★★★★★★★★★★★」 - 후속 Ajax 요청이 승인되지 않은 401에 실패했습니다.
- 웹 사이트를 새로 고칩니다(이 시점에서 페이지 헤더의 CSRF가 다른 것으로 변경됩니다).
- 로그아웃을 시도하고 다시 로그인 할 때까지 서명할 수 있습니다.
어떤 힌트라도 매우 감사합니다!더 자세한 내용을 추가할 수 있으면 알려주세요.
Jimbo는 당신이 직면한 문제의 배후에 있는 "이유"에 대해 훌륭하게 설명했습니다.이 문제를 해결하려면 다음 두 가지 방법을 사용할 수 있습니다.
(김보 추천)Override Gand:: SessionsController를 사용하여 새로운 csrf-token을 반환합니다.
class SessionsController < Devise::SessionsController def destroy # Assumes only JSON requests signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)) render :json => { 'csrfParam' => request_forgery_protection_token, 'csrfToken' => form_authenticity_token } end end클라이언트 측에서 sign_out 요구에 대한 성공 핸들러를 만듭니다(설정에 따라 GET vs DELETE 등 몇 가지 조정이 필요할 수 있습니다).
signOut: function() { var params = { dataType: "json", type: "GET", url: this.urlRoot + "/sign_out.json" }; var self = this; return $.ajax(params).done(function(data) { self.set("csrf-token", data.csrfToken); self.unset("user"); }); }또한 다음과 같은 모든 AJAX 요구에 CSRF 토큰을 자동으로 포함한다고 가정합니다.
$(document).ajaxSend(function (e, xhr, options) { xhr.setRequestHeader("X-CSRF-Token", MyApp.session.get("csrf-token")); });'보다 낫다', '보다 낫다', '보다 낫다', '보다 낫다', '보다 낫다'를 수 .
Devise::SessionsController.skip_before_filter :verify_authenticity_token.
저도 방금 이 문제에 부딪혔어요.여기 많은 일들이 있어.
TL;DR - CSRF 토큰이 서버 세션과 관련되어 있기 때문입니다(로그인 또는 로그아웃 여부에 관계없이 서버 세션이 있습니다).CSRF 토큰은 페이지가 로드될 때마다 DOM에 포함됩니다.로그아웃 시 세션은 리셋되고 CSRF 토큰은 없습니다.보통 로그아웃은 다른 페이지/액션으로 리다이렉트 되어 새로운 CSRF 토큰이 제공되지만, Ajax를 사용하고 있기 때문에 수동으로 이 작업을 수행해야 합니다.
- 새로운 CSRF 토큰을 반환하려면 Gand Session Controller::destroy 메서드를 덮어써야 합니다.
- 다음으로 클라이언트 측에서 로그아웃 XMLHttpRequest 성공 핸들러를 설정해야 합니다.에서는, 이 CSRF 「CSRF」의 dom 에 .
$('meta[name="csrf-token"]').attr('content', <NEW_CSRF_TOKEN>)
자세한 설명은 이쪽에서 확인하실 수 있습니다.protect_from_forgery다른 모든 컨트롤러가 상속되는 ApplicationController.rb 파일로 설정합니다(이것은 매우 일반적인 것이라고 생각합니다). protect_from_forgery는 모든 요구에 합니다.GET HTMLJavascript는 CSRF 체크입니다.Gander Login g POST 、 CSRF 、 CSRF g g g g g 。CSRF 체크에 실패하면 사용자의 현재 세션이 클리어 됩니다.즉, 서버는 CSRF 체크가 공격이라고 간주하기 때문에(이것은 올바른 동작 또는 바람직한 동작입니다).
따라서 로그아웃 상태에서 시작할 경우 새 페이지를 로드하고 페이지를 다시 로드하지 않습니다.
페이지 렌더링 시:서버는 서버 세션과 관련된 CSRF 토큰을 페이지에 삽입합니다.브라우저의 Javascript 콘솔에서 다음을 실행하여 이 토큰을 볼 수 있습니다.
$('meta[name="csrf-token"]').attr('content').그런 다음 XMLHttpRequest를 통해 로그인합니다.CSRF 토큰은 이 시점에서도 변경되지 않으므로 세션의 CSRF 토큰은 페이지에 삽입된 토큰과 일치합니다.백그라운드에서 클라이언트 측에서는 jquery-ujs가 xhr을 수신하고 다음 값을 사용하여 'X-CSRF-Token' 헤더를 설정합니다.
$('meta[name="csrf-token"]').attr('content')(CSRF).서버는 헤더에 설정된 토큰을 jquery-ujs와 세션 정보에 저장되어 있는 토큰을 비교하고 두 토큰이 일치하여 요청이 성공합니다.그런 다음 XMLHttpRequest를 통해 로그아웃합니다.그러면 세션이 리셋되고 CSRF 토큰이 없는 새 세션이 제공됩니다.
그런 다음 XMLHttpRequest를 통해 다시 로그인합니다.jquery-ujs는 다음 값에서 CSRF 토큰을 꺼냅니다.
$('meta[name="csrf-token"]').attr('content')이 값은 여전히 OLD CSRF 토큰입니다.이 오래된 토큰을 사용하여 'X-CSRF-Token'을 설정합니다.서버는 이 헤더 값을 세션에 추가하는 새로운 CSRF 토큰과 비교합니다.이것은 다릅니다.이 차이에 의해protect_form_forgeryWARNING: Can't verify CSRF token authenticity세션이 리셋되어 사용자가 로그아웃 됩니다.그런 다음 로그인 사용자가 필요한 다른 XMLHttpRequest를 만듭니다.현재 세션에 로그인한 사용자가 없으므로 401을 반환합니다.
업데이트: 8/14 Gand logout은 새로운 CSRF 토큰을 제공하지 않습니다.로그아웃 후에 일반적으로 발생하는 리다이렉트에서는 새로운 CSRF 토큰을 얻을 수 있습니다.
제 답변은 @Jimbo와 @Sija에서 많이 차용하고 있습니다만, 저는 Rails CSRF Protection + Angular.js: protect_from_forgular.js: POST에서 로그아웃을 하게 하고, 이것을 처음 했을 때 블로그에서 조금 상세하게 설명하고 있습니다.응용 프로그램컨트롤러에서는 csrf의 cookie를 설정하는 방법이 있습니다.
after_filter :set_csrf_cookie_for_ng
def set_csrf_cookie_for_ng
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end
@Sija 형식을 사용하고 있지만 이전 SO 솔루션의 코드를 사용하여 다음과 같은 이점을 얻을 수 있습니다.
class SessionsController < Devise::SessionsController
after_filter :set_csrf_headers, only: [:create, :destroy]
protected
def set_csrf_headers
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end
end
이 문제를 해결하는 데 몇 분이 걸렸기 때문에 세션컨트롤러를 오버라이드했음을 선언하기 위해 config/routes.rb를 변경해야 합니다.예를 들어 다음과 같습니다.
devise_for :users, :controllers => {sessions: 'sessions'}
또, 애플리케이션으로 실시한 대규모 CSRF 청소의 일부이기도 합니다.이것은 다른 사람에게도 흥미로울지도 모릅니다.블로그 투고는 이쪽입니다.기타 변경 사항은 다음과 같습니다.
Action Controller로부터의 구제:무효 인증토큰: 동기화가 안 되면 사용자가 쿠키를 지우는 대신 응용 프로그램이 자동으로 수정됩니다.레일에 놓여 있는 상태에서는 어플리케이션 컨트롤러의 디폴트 설정은 다음과 같습니다.
protect_from_forgery with: :exception
이 경우 다음 사항이 필요합니다.
rescue_from ActionController::InvalidAuthenticityToken do |exception|
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
render :error => 'invalid token', {:status => :unprocessable_entity}
end
또한 레이스 조건과 블로그 투고에서 자세히 코멘트한 Gande의 타임아웃 가능 모듈과의 상호 작용에 대해서도 약간의 문제가 있었습니다.요컨대 cookie_store가 아닌 active_record_store를 사용하는 것을 고려해 주십시오.또한 sign_in과 sign_out 액션에 가까운 병행 요구를 발행하는 것에 주의해야 합니다.
제 생각은 이렇습니다.
class SessionsController < Devise::SessionsController
after_filter :set_csrf_headers, only: [:create, :destroy]
respond_to :json
protected
def set_csrf_headers
if request.xhr?
response.headers['X-CSRF-Param'] = request_forgery_protection_token
response.headers['X-CSRF-Token'] = form_authenticity_token
end
end
end
클라이언트 측에서는, 다음과 같이 하고 있습니다.
$(document).ajaxComplete(function(event, xhr, settings) {
var csrf_param = xhr.getResponseHeader('X-CSRF-Param');
var csrf_token = xhr.getResponseHeader('X-CSRF-Token');
if (csrf_param) {
$('meta[name="csrf-param"]').attr('content', csrf_param);
}
if (csrf_token) {
$('meta[name="csrf-token"]').attr('content', csrf_token);
}
});
CSRF 되어 CSRF가 반환될 됩니다.X-CSRF-Token ★★★★★★★★★★★★★★★★★」X-CSRF-Param머리글
소스를해 본 , 이 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★.sign_out_all_scopes로로 합니다.false그럼 Warden이 세션 전체를 클리어할 수 없게 되므로 CSRF 토큰은 로그아웃 사이에 유지됩니다.
문제 태커의 설계 관련 설명: https://github.com/plataformatec/devise/issues/2200
방금 레이아웃 파일에 추가했는데 작동했어요
<%= csrf_meta_tag %>
<%= javascript_tag do %>
jQuery(document).ajaxSend(function(e, xhr, options) {
var token = jQuery("meta[name='csrf-token']").attr("content");
xhr.setRequestHeader("X-CSRF-Token", token);
});
<% end %>
이것을 application.js 파일에 포함했는지 확인합니다.
//=에는 jquery가 있습니다.
//=에는 jquery_ujs가 필요합니다.
그 이유는 기본적으로는 모든 Ajax 요구에 CSRF 토큰을 자동으로 설정하는 jquery-rails gem 이므로 이들 2개가 필요합니다.
제 경우 로그인 후 사용자 메뉴를 다시 그릴 필요가 있었습니다.이것으로 동작했습니다만, 같은 섹션(물론 페이지를 갱신하지 않고)에서 서버에 대한 모든 요구로 CSRF 인증 에러가 발생했습니다.js 뷰를 렌더링해야 했기 때문에 위의 솔루션이 작동하지 않았습니다.
제가 한 일은 Gand를 사용한 것입니다.
app/controllers/controller.displays
class SessionsController < Devise::SessionsController
respond_to :json
# GET /resource/sign_in
def new
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
yield resource if block_given?
if request.format.json?
markup = render_to_string :template => "devise/sessions/popup_login", :layout => false
render :json => { :data => markup }.to_json
else
respond_with(resource, serialize_options(resource))
end
end
# POST /resource/sign_in
def create
if request.format.json?
self.resource = warden.authenticate(auth_options)
if resource.nil?
return render json: {status: 'error', message: 'invalid username or password'}
end
sign_in(resource_name, resource)
render json: {status: 'success', message: '¡User authenticated!'}
else
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in)
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
end
end
그 후 controller #action에 메뉴를 다시 그려달라고 요청했습니다.Javascript에서 X-CSRF-Param과 X-CSRF-Token을 수정했습니다.
app/filename/redraw_user_menu.erb
$('.js-user-menu').html('');
$('.js-user-menu').append('<%= escape_javascript(render partial: 'shared/user_name_and_icon') %>');
$('meta[name="csrf-param"]').attr('content', '<%= request_forgery_protection_token.to_s %>');
$('meta[name="csrf-token"]').attr('content', '<%= form_authenticity_token %>');
같은 js 상황에 있는 사람에게 도움이 되었으면 합니다.
내 상황은 더 간단했다.제 경우, 제가 하고 싶었던 것은 이것뿐이었습니다.어떤 사람이 폼을 가진 화면에 앉아 있을 때 세션이 타임아웃(디바이스 타임아웃 가능 세션타임아웃)이 되면 보통 그 시점에서 Submit을 누르면 Gand는 로그인 화면으로 되돌립니다.글쎄, 난 그걸 원하지 않았어 왜냐면 그들은 양식 데이터를 다 잃어버렸거든.제출 폼을 잡기 위해 JavaScript를 사용합니다.Ajax는 사용자가 로그인하지 않았는지 여부를 판단하는 컨트롤러를 호출합니다.이 경우 사용자가 비밀번호를 재입력하는 폼을 올리고 Ajax 콜을 사용하여 재인증합니다(컨트롤러 내 bypass_sign_in).그러면 원래 제출 양식을 계속할 수 있습니다.
'위조로부터 보호'를 추가할 때까지 완벽하게 작동하고 있었어요
따라서 위의 답변에 따라 실제로 필요한 것은 컨트롤러에서 사용자를 다시 서명(bypass_sign_in)하는 것뿐입니다.이 경우 인스턴스 변수를 새로운 CSRF 토큰으로 설정하기만 하면 됩니다.
@new_csrf_token = form_authenticity_token
그 후 렌더링된 .js.erb에서 (이것도 XHR 콜이었으므로)
$('meta[name="csrf-token"]').attr('content', '<%= @new_csrf_token %>');
$('input[type="hidden"][name="authenticity_token"]').val('<%= @new_csrf_token %>');
Voila. 갱신되지 않아 이전 토큰으로 고정되어 있던 폼 페이지에 사용자 서명에서 얻은 새 세션의 새 토큰이 들어 있습니다.
@syslog4bit의 코멘트에 응답합니다.이 에러가 발생했을 경우는, 다음과 같이 됩니다.
Unexpected error while processing request: undefined method each for :authenticity_token:Symbol`
교체하다
response.headers['X-CSRF-Param'] = request_forgery_protection_token
와 함께
response.headers['X-CSRF-Param'] = request_forgery_protection_token.to_s
언급URL : https://stackoverflow.com/questions/11845500/rails-devise-authentication-csrf-issue
'programing' 카테고리의 다른 글
| 속성을 사용하여 스프링부트 @RestController를 활성화/비활성화할 수 있습니까? (0) | 2023.03.28 |
|---|---|
| Wordpress가 플러그인 설치에 사용하는 Linux 사용자를 어떻게 알 수 (0) | 2023.03.28 |
| JSON의 용량에 제한이 있습니까? (0) | 2023.03.28 |
| angular: 여러 종속 필드 검증 (0) | 2023.03.28 |
| 컴포넌트와 렌더 간의 리액트라우터 차이 (0) | 2023.03.28 |