Rails源代码分析(28):ActionController::RequestForgeryProtection

这是controller的最后一个module:

1 说明
Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. 

Only HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication scheme there anyway).  
Also, GET requests are not protected as these should be indempotent anyway.

This is turned on with the protect_from_forgery method, which will check the token and raise an
ActionController::InvalidAuthenticityToken if it doesn't match what was expected. 
You can customize the error message in production by editing public/422.html.  
A call to this method in ApplicationController is generated by default in post-Rails 2.0 applications.

The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form manually (without the
use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to include a hidden field named like that and set its value to what is returned by <tt>form_authenticity_token</tt>. Same applies to manually constructed Ajax requests. To make the token available through a global variable to scripts on a certain page, you could add something like this to a view:
    
       <%= javascript_tag "window._token = '#{form_authenticity_token}'" %>
    
Request forgery protection is disabled by default in test environment.  If you are upgrading from Rails 1.x, add this to
    # config/environments/test.rb:
    
       # Disable request forgery protection in test environment
       config.action_controller. allow_forgery_protection = false

  1.       # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
  2.       #
  3.       # Example:
  4.       #
  5.          class FooController < ApplicationController
  6.            # uses the cookie session store (then you don't need a separate :secret)
  7.            protect_from_forgery :except => :index
  8.       
  9.            # uses one of the other session stores that uses a session_id value.
  10.            protect_from_forgery :secret => 'my-little-pony':except => :index
  11.       
  12.            # you can disable csrf protection on controller-by-controller basis:
  13.            skip_before_filter :verify_authenticity_token
  14.          end
  15.       
  16.       # Valid Options:
  17.       #
  18.       # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call.  Set which actions are verified.
  19.       # * <tt>:secret</tt> - Custom salt used to generate the <tt>form_authenticity_token</tt>.
  20.       #   Leave this off if you are using the cookie session store.
  21.       # * <tt>:digest</tt> - Message digest used for hashing.  Defaults to 'SHA1'.

2 实现
  1.   module RequestForgeryProtection
  2.     def self.included(base)
  3.       base.class_eval do
  4.        # 增加了一个options变量,并且设置为空
  5.         class_inheritable_accessor :request_forgery_protection_options
  6.         self.request_forgery_protection_options = {}
  7.         # 增加了两个helper_method
  8.         helper_method :form_authenticity_token
  9.         helper_method :protect_against_forgery?
  10.       end
  11.       base.extend(ClassMethods)
  12.     end
  13.     
  14.    
  15.     module ClassMethods
  16.       
  17.       def protect_from_forgery(options = {})
  18.         self.request_forgery_protection_token ||= :authenticity_token
  19.         # 增加一个before_filter verify_authenticity_token
  20.         before_filter :verify_authenticity_token:only => options.delete(:only), :except => options.delete(:except)
  21.         request_forgery_protection_options.update(options)
  22.       end
  23.     end
  24.     protected
  25.       # The actual before_filter that is used.  Modify this to change how you handle unverified requests.
  26.       def verify_authenticity_token
  27.         verified_request? || raise(ActionController::InvalidAuthenticityToken)
  28.       end
  29.       
  30.       # Returns true or false if a request is verified.  Checks:
  31.       #
  32.       # * is the format restricted?  By default, only HTML and AJAX requests are checked.
  33.       # * is it a GET request?  Gets should be safe and idempotent
  34.       # * Does the form_authenticity_token match the given _token value from the params?
  35.       def verified_request?
  36.         !protect_against_forgery?     || # 1 如果不需要保护
  37.           request.method == :get      || # 2 如果是get方法
  38.           !verifiable_request_format? || # 3 是不是需要验证的格式
  39.           form_authenticity_token == params[request_forgery_protection_token] # 4 是不是正确的token
  40.       end
  41.     
  42.       def verifiable_request_format?
  43.         request.content_type.nil? || request.content_type.verify_request?
  44.       end
  45.     
  46.       # Sets the token value for the current session.  Pass a <tt>:secret</tt> option
  47.       # in +protect_from_forgery+ to add a custom salt to the hash.
  48.       def form_authenticity_token
  49.         @form_authenticity_token ||= if !session.respond_to?(:session_id) # 没有session_id
  50.           raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session.  Use #allow_forgery_protection to disable it, or use a valid session."
  51.         elsif request_forgery_protection_options[:secret
  52.           authenticity_token_from_session_id # 如果有secret 从session_id中获取token
  53.         elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
  54.           authenticity_token_from_cookie_session # 从cookie session中获取token
  55.         else
  56.           raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call.  Set that or use a session store capable of generating its own keys (Cookie Session Store)."
  57.         end
  58.       end
  59.       
  60.       # Generates a unique digest using the session_id and the CSRF secret.
  61.       def authenticity_token_from_session_id
  62.         key = if request_forgery_protection_options[:secret].respond_to?(:call)
  63.           request_forgery_protection_options[:secret].call(@session)
  64.         else
  65.           request_forgery_protection_options[:secret]
  66.         end
  67.         digest = request_forgery_protection_options[:digest] ||= 'SHA1'
  68.         OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
  69.       end
  70.       
  71.       # No secret was given, so assume this is a cookie session store.
  72.       def authenticity_token_from_cookie_session
  73.         session[:csrf_id] ||= CGI::Session.generate_unique_id
  74.         session.dbman.generate_digest(session[:csrf_id])
  75.       end
  76.       
  77.       def protect_against_forgery?
  78.         allow_forgery_protection && request_forgery_protection_token
  79.       end
  80.   end











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值