一、前言
出于數(shù)據(jù)安全性考慮,某些破壞性鏈接應(yīng)該使用post請(qǐng)求,比如一個(gè)刪除記錄的請(qǐng)求。
除了腳本確認(rèn)以外,服務(wù)端還需要post驗(yàn)證,因?yàn)槟_本是可以繞過的。想像你的頁面上有一個(gè)刪除鏈接,只作了客戶端腳本確認(rèn)(老的scaffold生成代碼有這問題),被google找到了,它一個(gè)請(qǐng)求就會(huì)讓你的數(shù)據(jù)丟失。
rails對(duì)于這類請(qǐng)求的處理,是通過verify方法,默認(rèn)的scaffold生成代碼有如下內(nèi)容:
??#?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請(qǐng)求時(shí),destroy才會(huì)被允許,如果是get,就會(huì)被重定向到list。
二、實(shí)現(xiàn)我自己實(shí)現(xiàn)了一個(gè)method_dispatch,當(dāng)請(qǐng)求一個(gè)/test/a時(shí),如果是get,則會(huì)直接執(zhí)行TestController#a;如果是post,則會(huì)執(zhí)行TestController#a_post,a_post應(yīng)該是protected,這樣不會(huì)直接暴露給客戶,get/post就嚴(yán)格區(qū)分開來了。
method_dispatch現(xiàn)在是直接實(shí)現(xiàn)在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里面的方法會(huì)被子類繼承到,所以必須嚴(yán)格處理訪問級(jí)別。
使用如下:
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要被保護(hù)起來防止直接調(diào)用。
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參數(shù)的link_to時(shí),生成的代碼如下:
<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>
經(jīng)測(cè)試上面代碼工作情況良好,使用get訪問/test/a時(shí),顯示get a;使用post訪問時(shí),顯示post a。使用get訪問/test/b時(shí),顯示get b;使用post時(shí),顯示get b,因?yàn)閎并沒有使用method_dispatch。
三、應(yīng)用下面的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中維護(hù)一份對(duì)應(yīng)方法名,稍不留神就容易出現(xiàn)漏洞。
我把它修改如下:
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
相應(yīng)地,還需要把new.rhtml中的action從create修改到new,把edit.rhtml中的action從update修改到edit。
這樣的修改把必須使用post請(qǐng)求的action隱藏起來,而相應(yīng)的get操作是不修改或刪除記錄的,如果以post請(qǐng)求,才會(huì)自動(dòng)調(diào)用這些保護(hù)的方法。