用了半年的Laravel,越來越喜歡他了,他大量的設計都和我的觀念相互印證,而且往往更加精妙和通用。今天就提筆記一個關于CSRF防護的問題。
我以前的方案
在我之前設計和使用的系統中,為了避免有人不小心寫出CSRF漏洞,我會全局禁止$_REQUEST的使用以及混用POST和GET的行為,要求開發人員分清每個傳入數據是POST還是GET。
于是我會在Reqeust對象上增加get($field_name, $default), post($field_name, $default) 的方法。并且直接在框架代碼啟動的時候將全局變量$_REQUEST置為空數組。
Laravel的方案
Part 1 Global CSRF Protection
Laravel推薦在全局注冊VerifyCsrfToken的Middleware,對所有Post,Put,Delete請求自動校驗是否帶合法的_csrf token。而要在表單中添加這個Token,只需要在form中加一行:
<?php echo csrf_field(); ?>
獲取表單值的方法:
$request->input('name', 'default name');
Part 2 路由層面區分Post/Get
// 同個Path下的Get,Post請求會根據配置不同進入不同的處理邏輯
Route::get('/path', 'XXXController@func_get');
Route::post('/path', 'XXXController@func_post');
我覺得Laravel這么設計比我高明在兩個地方:
對于新人,如果在Laravel中新增了一個表單,Post發現提交的時候提示CSRF校驗失敗,他很容易知道有這么個校驗,且開發環境下可以友好的提示他如何搞定這個問題。
而我的做法由于和PHP原生行為不一致,第一次碰到的人會很奇怪,就算花時間最終搞明白了,他也會覺得自己掉了個坑,因為我沒有任何提示。(其實現在想來是可以有提示的,應該將$_REQUEST改成一個每次調用都拋Exception給予提示的閉包)。Laravel可以方便的配置例外情況。本周即將發布的5.1-LTS版包涵一個我很期待的改進就是可以通過配置,忽略特定路徑下的CSRF校驗,以便兼容第三方代碼組件和第三方的Post數據回傳。詳見
依然可能有的問題
我之前的項目中,到后來,會發現有人寫這樣的代碼:
$val = $request->get('name', $request->post('name', 'defailt name'));
這么寫有時候是真的需要;但大多數時候只是因為懶/或者copy來的代碼自己也沒搞明白。
在Laravel中也無法完全避免這類問題,因為路由配置的時候除了Rouet::get() Route::post() 還有個大殺器 Route::any(),它同時兼容get和post請求,間接引入了同樣的問題。
遺憾
雖然在Laravel中,上述問題我可以增加UnitTest,檢查大家對any()的使用,但實在不夠優雅,誰有更好的思路,非常希望你能來信告訴我 icedfish@gmail.com。