Ruby 1.8.7:禁止批量调用打印链接,仅允许按钮触发
针对你遇到的问题——禁止用户直接在浏览器地址栏输入URL调用create_value_zone_printout接口,只能通过页面按钮触发,结合你使用的Ruby 1.8.7和老版本Rails(从路由写法判断应该是Rails 2.x),我整理了几个实用的解决方案:
方案1:将请求从GET改为POST(最直接有效)
GET请求天生支持地址栏直接访问,改成POST后,用户无法通过输入URL触发请求,必须通过表单或带指定method的链接提交,从根源上解决问题。
修改步骤:
- 更新路由配置:把GET请求改为POST
map.resources :zones, :collection => {:create_value_zone_printout => :post}
- 修改视图中的按钮链接:给
link_to添加:method => :post选项(Rails 2默认包含UJS脚本,支持这个功能)
<%= link_to 'sagatavot izdruku', {:controller => "zones", :action => "create_value_zone_printout", :value_zone => @zone.zone_name}, :class => "print", :method => :post %>
- 控制器新增请求方法校验:确保只有POST请求能进入逻辑
def create_value_zone_printout @service = "value_zones" if !public_user? return unless check_service_permissions end # 新增:校验请求方法为POST return permission_denied unless request.post? respond_to do |format| format.html do rep_id = Report.get_printout_file_id('VALUE_ZONE', session, {:value_zones => params[:value_zone]}) unless rep_id.blank? or rep_id.is_a?(Array) return permission_denied(SilentError.new("Kļūda veidojot izdruku!", rep_id.to_s, true)) end add_service_to_user_orders(nil, [params[:value_zone]], rep_id) unless public_user? file_data = Attachment.find_by_id(rep_id.first.keys.first) send_data(file_data.data_buffered, :filename => file_data.file_name) file_data.destroy if public_user? end end end
方案2:添加一次性签名验证(适合无法修改请求方法的场景)
如果因为业务限制不能改成POST,可以给每个按钮的URL生成会话绑定的唯一签名,服务器端验证签名有效性,防止用户篡改或批量构造URL。
修改步骤:
- 视图中生成签名:基于
value_zone和当前会话ID生成哈希签名(密钥建议存到配置文件)
<% value_zone = @zone.zone_name secret_key = "your_secure_custom_secret_key" # Ruby 1.8.7用Digest生成SHA1签名 signature = Digest::SHA1.hexdigest("#{value_zone}#{session[:session_id]}#{secret_key}") %> <%= link_to 'sagatavot izdruku', {:controller => "zones", :action => "create_value_zone_printout", :value_zone => value_zone, :signature => signature}, :class => "print" %>
- 控制器中验证签名:收到请求后重新计算签名,和参数中的签名对比,不一致则拒绝访问
def create_value_zone_printout @service = "value_zones" if !public_user? return unless check_service_permissions end # 签名验证逻辑 value_zone = params[:value_zone] provided_signature = params[:signature] secret_key = "your_secure_custom_secret_key" expected_signature = Digest::SHA1.hexdigest("#{value_zone}#{session[:session_id]}#{secret_key}") unless provided_signature == expected_signature return permission_denied(SilentError.new("Nederīga pieeja!", "Invalid request signature", true)) end respond_to do |format| format.html do rep_id = Report.get_printout_file_id('VALUE_ZONE', session, {:value_zones => params[:value_zone]}) unless rep_id.blank? or rep_id.is_a?(Array) return permission_denied(SilentError.new("Kļūda veidojot izdruku!", rep_id.to_s, true)) end add_service_to_user_orders(nil, [params[:value_zone]], rep_id) unless public_user? file_data = Attachment.find_by_id(rep_id.first.keys.first) send_data(file_data.data_buffered, :filename => file_data.file_name) file_data.destroy if public_user? end end end
这个签名和当前会话绑定,用户复制URL到其他浏览器或会话过期后就无法使用,能有效防止批量调用。
方案3:Referer检查(辅助验证,不单独使用)
可以检查请求的Referer是否来自你的应用内页面,但这个方法不可靠(用户可以伪造Referer),所以只能作为上面方案的补充:
def create_value_zone_printout # ... 其他逻辑 # 辅助校验Referer app_domain = "http://your_app_domain.com" unless request.referer && request.referer.starts_with?(app_domain) return permission_denied(SilentError.new("Nederīga pieeja!", "Invalid referer", true)) end # ... 其他逻辑 end
优先推荐方案1,因为POST请求符合HTTP规范,实现简单且从根本上避免了直接地址栏访问的问题。
内容的提问来源于stack exchange,提问作者user6158284
相关产品推荐
相关产品推荐

