[RoR] Post/Get分派

[RoR] Post/Get分派
一、前言

出于数据安全性考虑,某些破坏性链接应该使用post请求,比如一个删除记录的请求。

除了脚本确认以外,服务端还需要post验证,因为脚本是可以绕过的。想像你的页面上有一个删除链接,只作了客户端脚本确认(老的scaffold生成代码有这问题),被google找到了,它一个请求就会让你的数据丢失。

rails对于这类请求的处理,是通过verify方法,默认的scaffold生成代码有如下内容:

   #  GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
  verify  : method  =>   : post ,   : only  =>  [  : destroy ,   : create ,   : update ] ,
         
: redirect_to  =>  {  : action  =>   : list }

只有post请求时,destroy才会被允许,如果是get,就会被重定向到list。

二、实现

我自己实现了一个method_dispatch,当请求一个/test/a时,如果是get,则会直接执行TestController#a;如果是post,则会执行TestController#a_post,a_post应该是protected,这样不会直接暴露给客户,get/post就严格区分开来了。

method_dispatch现在是直接实现在ApplicationController中的,代码如下:

class ApplicationController  <  ActionController :: Base
  protected
  def self
. method_dispatch( * methods)
    before_filter 
: do_method_dispatch ,   : only  =>  methods . flatten . map ( &: to_sym)
  end

  private
  def do_method_dispatch
    
if  request . post ?   &&  respond_to ? ( " #{action_name}_post " )
      
eval ( " #{action_name}_post " )
      
return  false
    end
  end
end

由于ApplicationController里面的方法会被子类继承到,所以必须严格处理访问级别。

使用如下:

class TestController  <  ApplicationController
  method_dispatch 
: a

  def 
index
  end

  def a
    render 
: text  =>   ' get a '
  end
  def b
    render 
: text  =>   ' get b '
  end
  protected
  def a_post
    render 
: text  =>   ' post a '
  end
  def b_post
    render 
: text  =>   ' post b '
  end
end

注意a_post,b_post要被保护起来防止直接调用。

index.rhtml里面演示了使用get和post的情况:

<%=  link_to  " Get a " ,   : action  =>   ' a '   %>
<%=  link_to  " Post a " ,  { : action  =>   ' a ' } ,  { : post  =>  true}  %>< br  />

<%=  link_to  " Get b " ,   : action  =>   ' b '   %>
<%=  link_to  " Post b " ,  { : action  =>   ' b ' } ,  { : post  =>  true}  %>< br  />

rails在处理有:post => true参数的link_to时,生成的代码如下:

< href ="/test/a"  onclick ="var f = document.createElement('form');
       this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();return false;"
> Post a </ a >

经测试上面代码工作情况良好,使用get访问/test/a时,显示get a;使用post访问时,显示post a。使用get访问/test/b时,显示get b;使用post时,显示get b,因为b并没有使用method_dispatch。

三、应用

下面的posts_controller.rb是scaffold生成的:

class PostsController  <  ApplicationController
  def 
index
    list
    render 
: action  =>   ' list '
  end

  
#  GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
  verify  : method  =>   : post ,   : only  =>  [  : destroy ,   : create ,   : update ] ,
         
: redirect_to  =>  {  : action  =>   : list }

  def list
    
@post_pages ,   @posts   =  paginate  : posts ,   : per_page  =>   10
  end

  def show
    
@post   =  Post . find(params[ : id])
  end

  def new
    
@post   =  Post . new
  end

  def create
    
@post   =  Post . new(params[ : post])
    
if   @post . save
      flash[
: notice]  =   ' Post was successfully created. '
      redirect_to 
: action  =>   ' list '
    
else
      render 
: action  =>   ' new '
    end
  end

  def edit
    
@post   =  Post . find(params[ : id])
  end

  def update
    
@post   =  Post . find(params[ : id])
    
if   @post . update_attributes(params[ : post])
      flash[
: notice]  =   ' Post was successfully updated. '
      redirect_to 
: action  =>   ' show ' ,   : id  =>   @post
    
else
      render 
: action  =>   ' edit '
    end
  end

  def destroy
    Post
. find(params[ : id]) . destroy
    redirect_to 
: action  =>   ' list '
  end
end

可以看到,它添加了verify,但action过多,需要在verify中维护一份对应方法名,稍不留神就容易出现漏洞。

我把它修改如下:

class PostsController  <  ApplicationController
  method_dispatch 
: new ,   : edit ,   : destroy

  def 
index
    list
    render 
: action  =>   ' list '
  end

  def list
    
@post_pages ,   @posts   =  paginate  : posts ,   : per_page  =>   10
  end

  def show
    
@post   =  Post . find(params[ : id])
  end

  def new
    
@post   =  Post . new
  end

  def edit
    
@post   =  Post . find(params[ : id])
  end

  def destroy
    render 
: inline  =>   <<- EOS
      Are you sure?
      
<%=  link_to  " Yes " ,  {} ,   : post  =>  true  %>
      
<%=  link_to  " No " ,   : action  =>   ' edit ', :id => params[:id] %>
    EOS
  end

  protected
  def destroy_post
    Post
. find(params[ : id]) . destroy
    redirect_to 
: action  =>   ' list '
  end
  def edit_post
    
@post   =  Post . find(params[ : id])
    
if   @post . update_attributes(params[ : post])
      flash[
: notice]  =   ' Post was successfully updated. '
      redirect_to 
: action  =>   ' show ' ,   : id  =>   @post
    
else
      render 
: action  =>   ' edit '
    end
  end
  def new_post
    
@post   =  Post . new(params[ : post])
    
if   @post . save
      flash[
: notice]  =   ' Post was successfully created. '
      redirect_to 
: action  =>   ' list '
    
else
      render 
: action  =>   ' new '
    end
  end
end

相应地,还需要把new.rhtml中的action从create修改到new,把edit.rhtml中的action从update修改到edit。

这样的修改把必须使用post请求的action隐藏起来,而相应的get操作是不修改或删除记录的,如果以post请求,才会自动调用这些保护的方法。

你可能感兴趣的:([RoR] Post/Get分派)