面试题答案
一键面试CSRF 防护原理
CSRF 攻击利用用户已登录的身份,在用户不知情的情况下以用户的名义发送恶意请求。Rails 中 CSRF 防护原理是通过在每个表单和 AJAX 请求中添加一个独特的 CSRF 令牌(token)。服务器生成这个令牌并存储在用户的 session 中,当请求到达服务器时,服务器会检查请求中携带的 CSRF 令牌是否与 session 中存储的令牌一致。如果一致,则认为请求是合法的;如果不一致,则拒绝该请求,以此来防止 CSRF 攻击。
Rails 框架默认实现方式
- 表单:在 Rails 应用中,当使用
form_with
或form_tag
等帮助方法生成表单时,会自动在表单中添加一个隐藏的csrf-token
字段,其值是从用户 session 中获取的唯一令牌。例如:
<form action="/some_path" method="post">
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
<!-- 其他表单字段 -->
</form>
- AJAX 请求:对于 AJAX 请求,Rails 通过
turbolinks
或手动设置等方式,会在请求头中添加X-CSRF-Token
字段,其值同样是 session 中的 CSRF 令牌。在 Rails 应用的 JavaScript 环境中,rails-ujs
库会自动处理这个过程,确保 AJAX 请求携带正确的 CSRF 令牌。
自定义 CSRF 防护策略
- 令牌生成方式:
- 操作:可以自定义令牌的生成算法。例如,使用更复杂的加密算法生成令牌。在 Rails 中,可以通过覆盖
ActionController::RequestForgeryProtection
模块中的generate_csrf_token
方法来实现。首先,在application_controller.rb
中引入模块:
- 操作:可以自定义令牌的生成算法。例如,使用更复杂的加密算法生成令牌。在 Rails 中,可以通过覆盖
class ApplicationController < ActionController::Base
include ActionController::RequestForgeryProtection
def generate_csrf_token
# 自定义令牌生成逻辑,比如使用更复杂的加密函数
SecureRandom.urlsafe_base64(32)
end
end
- 令牌存储位置:
- 操作:默认情况下,CSRF 令牌存储在 session 中。如果要自定义,可以考虑将令牌存储在其他地方,如数据库或 Redis 中。以存储在 Redis 为例,首先需要安装 Redis 相关 gem(如
redis
)。然后在application_controller.rb
中修改validate_authenticity_token
方法来从 Redis 中获取和验证令牌:
- 操作:默认情况下,CSRF 令牌存储在 session 中。如果要自定义,可以考虑将令牌存储在其他地方,如数据库或 Redis 中。以存储在 Redis 为例,首先需要安装 Redis 相关 gem(如
require 'redis'
redis = Redis.new
class ApplicationController < ActionController::Base
include ActionController::RequestForgeryProtection
def validate_authenticity_token
session_token = session[:_csrf_token]
redis_token = redis.get("#{session_id}:csrf_token")
if session_token && session_token == redis_token
super
else
raise ActionController::InvalidAuthenticityToken
end
end
end
- 请求验证时机:
- 操作:默认在请求到达控制器时验证 CSRF 令牌。可以自定义验证时机,比如在 Rack 中间件级别进行验证。创建一个自定义的 Rack 中间件,例如在
lib/csrf_middleware.rb
中:
- 操作:默认在请求到达控制器时验证 CSRF 令牌。可以自定义验证时机,比如在 Rack 中间件级别进行验证。创建一个自定义的 Rack 中间件,例如在
class CsrfMiddleware
def initialize(app)
@app = app
end
def call(env)
request = ActionDispatch::Request.new(env)
if request.post? || request.put? || request.delete?
# 手动验证 CSRF 令牌逻辑
session = request.session
token = request.headers['X-CSRF-Token'] || request.params['authenticity_token']
if token && token == session[:_csrf_token]
@app.call(env)
else
[403, {'Content-Type' => 'text/plain'}, ['Forbidden - CSRF token mismatch']]
end
else
@app.call(env)
end
end
end
然后在 config/application.rb
中注册这个中间件:
class Application < Rails::Application
config.middleware.insert_before ActionDispatch::Callbacks, CsrfMiddleware
end