API安全架構之抽絲剝繭
前言
這篇文章是構建安全API系列的第2部分。 點擊這裡查看更多的系列文章!
在本系列的前一部分中,我花了一點時間,介紹了我們將要創建的安全API所需要實施的基本概念和過程。我還介紹了我們來創建API將要使用到的一些工具。如果你還沒閱讀本系列文章的第一部分,我一定會在你繼續閱讀本文前強烈推薦你去閱讀一下第一部分。在本系列的第二部分中,我將從Slim Framework開始,深入挖掘這些工具。
我從框架作為開始的原因是因為它將成為我們構建一切的基礎。我們的所有構建思路和功能將包含在整個框架的結構內。現在PHP生態系統中有很多框架可供選擇。我選擇使用Slim框架,是因為在我看來,它提供了最低限度的入門條件,並不需要太多的解釋說明就可以快速上手,並且如果你需要一些功能的話,一切都可以放在一個文件中進行實現。
由於這是一個由多個部分組成的系列博文,我想確保在提供的代碼方面能有更多的清晰度。所以,我創建了一個存儲庫可用於跟蹤本系列文章每一部分發生的更改。存儲庫里包含了本系列每個部分的分支,每個部分都是基於了上一部分的功能。他們將顯示本系列中每個教程的最終結果,以便你可以跟隨每個教程,看看它是如何構建的,或者只是使用存儲庫來查看最終的結果。
另外我還會提到我們將要編寫的代碼 - 我將會使用幾個PHP包,因此我們不會重新創建已經編寫過和測試過的大量代碼。所以,你需要熟悉Composer依賴關係管理工具。我們將大量使用這些工具來提取軟體包,並使得這些軟體包易於使用到我們的應用程序中。
關於Web伺服器?
雖然你可以設置你所選擇的Web伺服器來處理進入Slim應用程序的請求,但我將在此保持一種簡單的風格。PHP擁有自己的內置Web伺服器,非常適合這樣的示例。它允許你定義並將index.php作為應用程序的「前端控制器」,同時在你選擇的埠上提供服務。
有一件事需要注意 - 當我使用內置的PHP伺服器作為我的示例中的Web服務時,它不應該被用來替代更強大的網路伺服器,如Apache或Nginx。它非常適合測試和本地開發環境,但在性能質量部門肯定會希望你選擇更可靠性能更好的Web伺服器。如果你在生產環境中使用了內置的PHP Web伺服器,那麼可能會給你帶來麻煩。
讓我們使用內置的伺服器來獲取一個簡單的腳本並進行運行,以便你可以看到這個腳本的工作原理。你可以在系統的任何位置創建我們的測試文件,只要目錄中有index.php這個文件就行。如果你要進行動手嘗試,通常我建議你創建一個臨時目錄。在本教程中,我將在基於Unix的系統上執行命令,但在Windows上這個過程也是類似的。
創建文件只是一些比較簡單的命令:
mkdir www-tmpnecho <?php phpinfo(); ?> test.phpn
然後,我們有一個PHP文件,test.php,其中包含了對phpinfo函數的調用,並且會顯示我們的PHP實例的所有配置選項。
現在讓我們啟動內置的PHP伺服器:
> php -S test.phpnphp -S localhost:8000 test.phpnPHP 7.0.12 Development Server started at Wed Apr 19 16:40:00 2017nListening on http://localhost:8000nDocument root is /path/to/scriptnPress Ctrl-C to quit.n
然後訪問http://localhost:8000,你可以在瀏覽器中查看並可以看到正常的phpinfo輸出。
需要注意的一點是,如果你的目錄中有index.php文件,並且沒有指定第二個參數,那麼框架將使用index.php作為默認的執行腳本。
簡單吧?現在我們知道如何使用內置的伺服器了,我們可以繼續安裝Slim Framework然後用於構建我們的API。
安裝Slim框架
由於本系列文章的這一部分都是關於設置應用程序的初始部分,所以我們從基礎開始:Slim框架。感謝Composer,要將它使用到我們的應用程序,可以使用基本的require命令:
> composer require slim/slimn nUsing version ^3.8 for slim/slimn./composer.json has been updatednLoading composer repositories with package informationnUpdating dependencies (including require-dev)n[...]nWriting lock filenGenerating autoload filesn
如果你還不熟悉Composer軟體包管理器,請參閱「 入門指南」了解更多信息。
執行上面的命令將安裝基本的Slim框架及其相關組件,如nikic/fast-route請求路由組件和pimple/pimple依賴注入庫。Slim框架大量使用了Pimple容器來處理應用程序中的依賴關係。大多數請求和響應處理圍繞著這個容器。
如果Composer的安裝過程一切順利的話,它應該會生成自動載入的文件,並創建一個composer.lock文件,其中包含了有關於你安裝的Slim版本的詳細信息。如果有錯誤,你需要在繼續操作之前對其進行調試 - 你必須先安裝Slim及其依賴項,然後再嘗試開發以下代碼。
創建我們的第一個路由
隨著Slim的組件的全部安裝完成,但由於Composer為我們設置了自動載入(PSR-0或PSR-4),我們可以直接在我們的腳本中使用它。Slim創建路由的過程非常簡單,在我們的一個文件中定義路由 - 不需要創建任何目錄或額外的文件。然而,這既具有有優點又有缺點。能夠將東西全部保存在一個地方是很方便的做法,但是當整個項目開始變得複雜時,單個文件就使得維護更加困難。
不過,我們先從一些簡單的代碼開始,所以讓我們創建第一個路由。首先創建一個index.php文件並將下面的代碼放在其中:
require_once vendor/autoload.php;n n$app = new SlimApp();n$app->get(/, function() {necho It works!;n});n$app->run();n
現在,如果我們啟動PHP內置的伺服器,並在瀏覽器中載入它,你應該可以看到在根目錄的頁面會列印出「It works!」。在上面的代碼中,我們已經在根路徑/上定義了一個GET請求的路由。當我們開始添加其他路由類型時,事情會變得更加複雜,但是Slim手冊中有很多信息,所以我不會在這裡重新進行整理說明。
現在,我們已經創建了一個基本的應用程序,我們可以繼續改變一些關於它如何工作以滿足我們的API需求。
使用配置
Slim框架的一個主要功能是使用依賴注入容器定義配置值和你在應用程序中使用的其他資源。這還包括一些特殊的設置,Slim在內部重寫了默認的錯誤處理程序。它也可以作為我們的控制器的存儲庫,使其更容易直接在我們的路由中使用它們。
我們先從這些自定義的設置開始,這樣做有助於更好的構建我們的Slim API。這些配置是針對Slim的處理程序的,但實現這些處理程序的思路是一樣的,可以在任何框架內重新實現(有些甚至可以為你自動執行)。
notFoundHandler
當在定義的路由的列表中找不到被調用的URL時,Slim就會使用這個處理程序。有了這個錯誤處理程序,當客戶端用戶調用不正確時,我們可以設置一個友好的API響應。這裡有一個例子:
$app = new SlimApp();n$container = $app->getContains();n n$container[notFoundHandler] = function($container) {nreturn function ($request, $response) use ($container) {nreturn $container[response]n->withStatus(404)n->withHeader(Content-Type, application/json)n->write(json_encode([error => Resource not valid]));n};n};n
如果你不習慣使用PSR-7結構,那麼代碼可能會有點複雜,但是當你將其拆下來後會發現它其實很簡單。本質上,如果調用不存在的URL,我們將返回一個HTTP狀態為404 的對象response和一個值為「資源無效」 的JSON消息error:
{nerror: "Resource not valid"n}n
然後Slim在端點未定義時內部使用此處理程序 – 你無需做其他任何工作。
notAllowedHandler
Slim使用的另一個特殊設置就是notAllowedHandler。當不允許調用端點的方法時,框架會使用此處理程序,比如使用GET請求去調用一個POST端點:
$container[notAllowedHandler] = function($container) {nreturn function ($request, $response) use ($container) {nreturn $container[response]n->withStatus(401)n->withHeader(Content-Type, application/json)n->write(json_encode([error => Method not allowed]));n};n};n
它與上面的notFoundHandler非常相似,只是在響應中有不同的內容:401和「不允許的方法」的錯誤消息。
errorHandler
我想在這裡需要提到的最後一個特殊的選項就是使用errorHandler。在我選擇的應用程序結構中,我們將利用PHP的異常處理特性在出現錯誤時停止執行,並向用戶報告錯誤。這樣可以防止我們在我們的控制器內部直接輸出JSON並在一些奇怪的地方做任何其他比較詭異的事情。如果發現錯誤,就應該立即返回,而不是繼續往下執行。
該errorHandler比前面的例子更詳細一點。示例代碼如下:
$container[errorHandler] = function($container) {nreturn function ($request, $response, $exception = null) use ($container) {n$code = 500;n$message = There was an error;n nif ($exception !== null) {n$code = $exception->getCode();n$message = $exception->getMessage();n}n n// Use this for debugging purposesn/*error_log($exception->getMessage(). in .$exception->getFile(). - (n.$exception->getLine()., .get_class($exception).));*/n nreturn $container[response]n->withStatus($code)n->withHeader(Content-Type, application/json)n->write(json_encode([nsuccess => false,nerror => $messagen]));n};n};n
現在,這個處理程序應該看起來很熟悉了,具有相同的功能簽名和返回相同的響應對象。但有一些差異,主要是處理例外的情況。你會注意到$exception在內部閉包中有一個新的參數。這是通過Slim的內部功能傳遞給了錯誤處理程序,因此我們可以評估拋出的異常。在我們的例子中,我們設置了一個默認值$code,如果未設置異常(並非所有錯誤都是異常)則為$message,然後如果設置了異常,則會更新這些值。在我們的設置中,我們將使用異常的「代碼」值作為我們返回的HTTP狀態代碼。這讓我們控制了控制器內部的response對象然後使用此代碼返回該對象,該異常中提供的消息和一個值為false的success對象。在我們的API中使用這個success對象值的返回結構都是一致的,並且將與每個響應一起返回(保存錯誤響應)。在我們的控制器中,我們可以拋出異常,這樣errorHandler就可以正確地處理它們了:
$app = new SlimApp();n$app->get(/, function() {nthrow new Exception("You shouldnt have done that!", 418);n});n$app->run();n
這將導致如下的響應內容:
{nsuccess: false,nerror: "You shouldnt have done that!"n}n
響應中的HTTP狀態碼將在路由中的異常throw中指定。最後,你還可以使用自定義的異常,通過在類中定義它們,並使自定義的異常更具可重用性(例如CouldNotSaveRecord或InvalidInput異常)。
最後一件事
我想在轉到本系列的下一部分之前,做最後一次更新。在我們目前的設置中,根路徑/響應了一些純文本的「工作」。這不是很友好的API,現在我們的其他錯誤處理程序是支持API構建的,當然你也可以改變這一點。Slim的請求/響應處理可以使這種改變變得很容易。我們只是返回一個Response對象作為一個新的實例這與錯誤處理程序的輸出類似:
$app->get(/, function() {nreturn $response->withHeader(Content-Type, application/json)n->write(json_encode([message => It works!]));n});n
現在,當我們訪問到根路徑的路由/時,響應將返回一個內容類型為application/json的JSON內容:
{nmessage: "It works!"n}n
現在,安裝完成...
在本文中,我介紹了一些設置Slim框架的基礎知識,創建一個應用程序並配置幾個處理程序,以幫助後續的事情變得更簡單。還有其他我們將來會遇到的配置選項,但這就是你開始的地方。
為了幫助你更輕鬆地跟蹤本系列的文章和每次創建的代碼,我將為GitHub存儲庫中的每個部分添加一個分支。分支中的代碼將是每篇文章的最終結果,所以希望到本系列文章的最後,我們將有一個完整的API示例,你將來可以使用它作為構建安全API的指導。
存儲庫位於此處:https://github.com/psecio/secure-api。
所以,讓我們繼續構建這個項目,並嘗試一些請求示例和我們在本文中提到的例子。
資源
Slim 框架
第一部分
推薦閱讀:
※最好的開源或開放API的ocr引擎是什麼?
※如何評價快速閱讀(speed read)技術?
※語音識別開放化開發平台有哪些?