一、前言
出于数据安全性考虑,某些破坏性链接应该使用post请求,比如一个删除记录的请求。
除了脚本确认以外,服务端还需要post验证,因为脚本是可以绕过的。想像你的页面上有一个删除链接,只作了客户端脚本确认(老的scaffold生成代码有这问题),被google找到了,它一个请求就会让你的数据丢失。
rails对于这类请求的处理,是通过verify方法,默认的scaffold生成代码有如下内容:
只有post请求时,destroy才会被允许,如果是get,就会被重定向到list。
二、实现
我自己实现了一个method_dispatch,当请求一个/test/a时,如果是get,则会直接执行TestController#a;如果是post,则会执行TestController#a_post,a_post应该是protected,这样不会直接暴露给客户,get/post就严格区分开来了。
method_dispatch现在是直接实现在ApplicationController中的,代码如下:
由于ApplicationController里面的方法会被子类继承到,所以必须严格处理访问级别。
使用如下:
注意a_post,b_post要被保护起来防止直接调用。
index.rhtml里面演示了使用get和post的情况:
rails在处理有:post => true参数的link_to时,生成的代码如下:
经测试上面代码工作情况良好,使用get访问/test/a时,显示get a;使用post访问时,显示post a。使用get访问/test/b时,显示get b;使用post时,显示get b,因为b并没有使用method_dispatch。
三、应用
下面的posts_controller.rb是scaffold生成的:
可以看到,它添加了verify,但action过多,需要在verify中维护一份对应方法名,稍不留神就容易出现漏洞。
我把它修改如下:
相应地,还需要把new.rhtml中的action从create修改到new,把edit.rhtml中的action从update修改到edit。
这样的修改把必须使用post请求的action隐藏起来,而相应的get操作是不修改或删除记录的,如果以post请求,才会自动调用这些保护的方法。
出于数据安全性考虑,某些破坏性链接应该使用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 }
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
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
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 />
<%= 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时,生成的代码如下:
<
a
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 >
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
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
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请求,才会自动调用这些保护的方法。