兩周前因?yàn)楣疽淮尾萌?,好幾個(gè)人的活都被按在了我頭上,這其中的一大部分是一系列REST API,,撰寫者號稱基本完成,,我測試了一下,,發(fā)現(xiàn)盡管從功能的角度來說,這些API實(shí)現(xiàn)了spec的顯式要求,,但是從實(shí)際使用的角度,,欠缺的東西太多(各種各樣的隱式需求)。REST API是一個(gè)系統(tǒng)的backend和frontend(或者3rd party)打交道的通道,,承前啟后,,有很多很多隱式需求,比如調(diào)用接口與RFC保持一致,,API的內(nèi)在和外在的安全性等等,,并非提供幾個(gè)endpoint,返回相應(yīng)的json數(shù)據(jù)那么簡單,。仔細(xì)研究了原作者的代碼,,發(fā)現(xiàn)缺失的東西實(shí)在太多,每個(gè)API基本都在各自為戰(zhàn),,與其修補(bǔ),,不如重寫(并非是程序員相輕的緣故),于是我花了一整周,,重寫了所有的API,。稍稍總結(jié)了些經(jīng)驗(yàn),在這篇文章里講講如何撰寫「合格的」REST API,。 RFC一致性REST API一般用來將某種資源和允許的對資源的操作暴露給外界,,使調(diào)用者能夠以正確的方式操作資源。這里,,在輸入輸出的處理上,,要符合HTTP/1.1(不久的將來,要符合HTTP/2.0)的RFC,,保證接口的一致性,。這里主要講輸入的method/headers和輸出的status code。 MethodsHTTP協(xié)議提供了很多methods來操作數(shù)據(jù):
冪等在HTTP/1.1中定義如下:
簡單說來就是一個(gè)操作符合冪等性,,那么相同的數(shù)據(jù)和參數(shù)下,,執(zhí)行一次或多次產(chǎn)生的效果(副作用)是一樣的。 現(xiàn)在大多的REST framwork對HTTP methods都有正確的支持,,有些舊的framework可能未必對PATCH有支持,,需要注意。如果自己手寫REST API,,一定要注意區(qū)分POST/PUT/PATCH/DELETE的應(yīng)用場景,。 Headers很多REST API犯的比較大的一個(gè)問題是:不怎么理會request headers。對于REST API,,有一些HTTP headers很重要:
Status Code很多REST API犯下的另一個(gè)錯(cuò)誤是:返回?cái)?shù)據(jù)時(shí)不遵循RFC定義的status code,而是一律200 ok + error message,。這么做在client + API都是同一公司所為還湊合可用,,但一旦把API暴露給第三方,不但貽笑大方,,還會留下諸多互操作上的隱患,。 以上僅僅是最基本的一些考慮,要做到完全符合RFC,,除了參考RFC本身以外,,erlang社區(qū)的webmachine或者clojure下的liberator都是不錯(cuò)的實(shí)現(xiàn),是目前為數(shù)不多的REST API done right的library/framework,。 (liberator的decision tree,,沿襲了webmachine的思想,請自行g(shù)oogle其文檔查看大圖) 安全性前面說過,,REST API承前啟后,,是系統(tǒng)暴露給外界的接口,所以,,其安全性非常重要,。安全并單單不意味著加密解密,而是一致性(integrity),,機(jī)密性(confidentiality)和可用性(availibility),。 請求數(shù)據(jù)驗(yàn)證我們從數(shù)據(jù)流入REST API的第一步 —— 請求數(shù)據(jù)的驗(yàn)證 —— 來保證安全性。你可以把請求數(shù)據(jù)驗(yàn)證看成一個(gè)巨大的漏斗,,把不必要的訪問統(tǒng)統(tǒng)過濾在第一線:
數(shù)據(jù)完整性驗(yàn)證REST API往往需要對backend的數(shù)據(jù)進(jìn)行修改,。修改是個(gè)很可怕的操作,我們既要保證正常的服務(wù)請求能夠正確處理,,還需要防止各種潛在的攻擊,如replay,。數(shù)據(jù)完整性驗(yàn)證的底線是:保證要修改的數(shù)據(jù)和服務(wù)器里的數(shù)據(jù)是一致的 —— 這是通過Etag來完成,。 Etag可以認(rèn)為是某個(gè)資源的一個(gè)唯一的版本號。當(dāng)客戶端請求某個(gè)資源時(shí),,該資源的Etag一同被返回,,而當(dāng)客戶端需要修改該資源時(shí),需要通過"If-Match"頭來提供這個(gè)Etag,。服務(wù)器檢查客戶端提供的Etag是否和服務(wù)器同一資源的Etag相同,,如果相同,才進(jìn)行修改,,否則返回412 precondition failed,。 使用Etag可以防止錯(cuò)誤更新。比如A拿到了Resource X的Etag X1,,B也拿到了Resource X的Etag X1,。B對X做了修改,修改后系統(tǒng)生成的新的Etag是X2,。這時(shí)A也想更新X,,由于A持有舊的Etag,服務(wù)器拒絕更新,,直至A重新獲取了X后才能正常更新,。 Etag類似一把鎖,是數(shù)據(jù)完整性的最重要的一道保障,。Etag能把絕大多數(shù)integrity的問題扼殺在搖籃中,,當(dāng)然,race condition還是存在的:如果B的修改還未進(jìn)入數(shù)據(jù)庫,,而A的修改請求正好通過了Etag的驗(yàn)證時(shí),,依然存在一致性問題。這就需要在數(shù)據(jù)庫寫入時(shí)做一致性寫入的前置檢查,。 訪問控制REST API需要清晰定義哪些操作能夠公開訪問,,哪些操作需要授權(quán)訪問。一般而言,,如果對REST API的安全性要求比較高,,那么,所有的API的所有操作均需得到授權(quán),。 在HTTP協(xié)議之上處理授權(quán)有很多方法,,如HTTP BASIC Auth,,OAuth,HMAC Auth等,,其核心思想都是驗(yàn)證某個(gè)請求是由一個(gè)合法的請求者發(fā)起,。Basic Auth會把用戶的密碼暴露在網(wǎng)絡(luò)之中,并非最安全的解決方案,,OAuth的核心部分與HMAC Auth差不多,,只不過多了很多與token分發(fā)相關(guān)的內(nèi)容。這里我們主要講講HMAC Auth的思想,。 回到Security的三個(gè)屬性:一致性,,機(jī)密性,和可用性,。HMAC Auth保證一致性:請求的數(shù)據(jù)在傳輸過程中未被修改,,因此可以安全地用于驗(yàn)證請求的合法性。 HMAC主要在請求頭中使用兩個(gè)字段:Authorization和Date(或X-Auth-Timestamp),。Authorization字段的內(nèi)容由":"分隔成兩部分,,":"前是access-key,":"后是HTTP請求的HMAC值,。在API授權(quán)的時(shí)候一般會為調(diào)用者生成access-key和access-secret,,前者可以暴露在網(wǎng)絡(luò)中,后者必須安全保存,。當(dāng)客戶端調(diào)用API時(shí),,用自己的access-secret按照要求對request的headers/body計(jì)算HMAC,然后把自己的access-key和HMAC填入Authorization頭中,。服務(wù)器拿到這個(gè)頭,,從數(shù)據(jù)庫(或者緩存)中取出access-key對應(yīng)的secret,按照相同的方式計(jì)算HMAC,,如果其與Authorization header中的一致,,則請求是合法的,且未被修改過的,;否則不合法,。 GET /photos/puppy.jpg HTTP/1.1 Host: johnsmith.s3.amazonaws.com Date: Mon, 26 Mar 2007 19:37:58 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE:frJIUN8DYpKDtOLCwo//yllqDzg= (Amazon HMAC圖示) 在做HMAC的時(shí)候,request headers中的request method,,request URI,,Date/X-Auth-Timestamp等header會被計(jì)算在HMAC中。將時(shí)間戳計(jì)算在HMAC中的好處是可以防止replay攻擊,??蛻舳撕头?wù)器之間的UTC時(shí)間正常來說偏差很小,那么,,一個(gè)請求攜帶的時(shí)間戳,,和該請求到達(dá)服務(wù)器時(shí)服務(wù)器的時(shí)間戳,,中間差別太大,超過某個(gè)閾值(比如說120s),,那么可以認(rèn)為是replay,,服務(wù)器主動丟棄該請求。 使用HMAC可以很大程度上防止DOS攻擊 —— 無效的請求在驗(yàn)證HMAC階段就被丟棄,,最大程度保護(hù)服務(wù)器的計(jì)算資源,。 HTTPSHMAC Auth盡管在保證請求的一致性上非常安全,可以用于鑒別請求是否由合法的請求者發(fā)起,,但請求的數(shù)據(jù)和服務(wù)器返回的響應(yīng)都是明文傳輸,對某些要求比較高的API來說,,安全級別還不夠,。這時(shí)候,需要部署HTTPS,。在其之上再加一層屏障。 其他做到了接口一致性(符合RFC)和安全性,REST API可以算得上是合格了,。當(dāng)然,,一個(gè)實(shí)現(xiàn)良好的REST API還應(yīng)該有如下功能:
各個(gè)社區(qū)里面比較成熟的REST API framework/library:
其它語言接觸不多,,就不介紹了??梢酝ㄟ^訪問該語言在github上相應(yīng)的awesome repo(google awesome XXX,,如awesome python),查看REST API相關(guān)的部分,。 如果您覺得這篇文章不錯(cuò),,請點(diǎn)贊。多謝,! 歡迎訂閱公眾號『程序人生』(搜索微信號 programmer_life),。每篇文章都力求原汁原味,北京時(shí)間中午12點(diǎn)左右,,美西時(shí)間下午8點(diǎn)左右與您相會,。 |
|