[譯] 怎樣使用GraphQL - 進階 - 4.安全

英文原版地址:https://www.howtographql.com/...git

GraphQL爲客戶端提供強大的能力。可是擁有強大的能力時,也會帶來更大的風險。github

因爲客戶端有可能使用很是複雜的查詢,所以咱們的服務器必須可以妥善處理。這些查詢多是來自惡意客戶端的濫用查詢,或者可能只是合法客戶端使用的很是大的查詢。在這兩種狀況下,客戶端可能會將您的GraphQL服務器崩潰。算法

咱們將在本章中介紹一些減輕這些風險的策略。咱們將以最簡單到最複雜的順序來講明,並看看這些方式的利弊。ruby

超時策略

第一個策略,也是最簡單的策略是使用簡單的超時來防範大型查詢。這不須要服務器瞭解有關傳入查詢的任何內容。服務器須要知道的僅僅是容許查詢的最長時間。服務器

例如,配置了5秒超時的服務器將中止執行超過5秒鐘執行的任何查詢。架構

超時的優點

  • 操做簡單ide

  • 大多數策略都會使用超時做爲最終保護post

超時的缺點

  • 即便有超時策略,也可能會形成很差的後果ui

  • 有時難以實施。在一段時間以後切斷鏈接可能會致使奇怪的行爲。spa

最大查詢深度

正如咱們以前所述,使用GraphQL的客戶能夠隨意寫出任意的複雜查詢。因爲GraphQL模式一般是嵌套的,這意味着客戶端能夠寫出以下所示的查詢:

query IAmEvil {
  author(id: "abc") {
    posts {
      author {
        posts {
          author {
            posts {
              author {
                # that could go on as deep as the client wants!
              }
            }
          }
        }
      }
    }
  }
}

若是咱們能夠阻止客戶濫用這樣的查詢深度呢? 在瞭解定義的模式時,可讓你瞭解合法查詢的深度。這其實是能夠實現的,而且一般稱爲最大查詢深度。

經過分析查詢文檔的AST,GraphQL服務器可以根據其深度拒絕或接受請求。

例如,配置了最大查詢深度爲3的服務器,以及如下查詢文檔。紅色方框選中的全部內容都被認爲深度太深,查詢無效。

圖片描述

使用最大查詢深度設置的graphql-ruby服務,咱們獲得如下返回結果:

{
  "errors": [
    {
      "message": "Query has depth of 6, which exceeds max depth of 3"
    }
  ]
}

最大查詢深度優勢

  • 因爲靜態分析了文檔的AST,所以查詢甚至不執行,因此不會在GraphQL服務器上增長負擔。

最大查詢深度缺點

  • 只有深度每每不足以涵蓋全部濫用查詢。 例如,在根節點上請求大量的查詢將是代價巨大的,但不太可能被查詢深度分析器阻止。

查詢複雜性

有時,查詢的深度還不足以真正瞭解GraphQL查詢的開銷。在不少狀況下,咱們的模式中的某些字段比其餘字段更復雜。

查詢複雜性容許您定義這些字段的複雜程度,並限制最大複雜度的查詢。這個想法是經過使用一個簡單的數字來定義每一個字段的複雜程度。一個常見的默認設置是給每一個字段一個複雜的1。以這個查詢爲例:

query {
  author(id: "abc") { # complexity: 1
    posts {           # complexity: 1
      title           # complexity: 1
    }
  }
}

一個簡單的加法,告訴咱們查詢的複雜性是3。若是咱們在咱們的架構上設置最大複雜度爲2,則此查詢將會失敗。

若是posts字段實際上比做者字段複雜度高不少呢?咱們能夠爲該領域設置不一樣的複雜性。咱們甚至能夠根據參數設置不一樣的複雜性! 咱們來看看一個相似的查詢,其中posts會根據傳入的參數去肯定複雜性:

query {
  author(id: "abc") {    # complexity: 1
    posts(first: 5) {    # complexity: 5
      title              # complexity: 1
    }
  }
}

查詢複雜性的優勢

  • 能夠覆蓋比更多的用例。

  • 經過靜態分析複雜性,在執行前拒絕查詢。

查詢複雜性缺點

  • 很難實現完美

  • 若是須要開發時預估複雜性,咱們如何保持狀態最新?咱們一開始怎麼能知道查詢成本?

  • Mutations 很難估計。若是他們有一個難以衡量的附加操做,如在後臺排隊執行的任務怎麼辦?

節流

到目前爲止,咱們看到的解決方案都是會阻止濫用服務器的查詢。像這樣使用它們的問題是,它們會阻止大量查詢,但不會阻止客戶端生成出大量查詢!

在大多數API中,使用簡單的節流方式是,阻止客戶端頻繁地請求資源。GraphQL有點特別,由於調節請求數並無真正幫助咱們。即便是不多的請求也多是大量的查詢。

事實上,咱們不知道客戶端定義了多少請求是能夠接受的。那麼咱們如何來限制客戶端呢?

基於服務器執行時間的調節

咱們能夠經過查詢執行時的服務器耗時,來估計查詢的複雜程度。咱們可使用這種式來限制查詢。憑藉對系統的瞭解,您能夠提出客戶端能夠在特定時間範圍內使用的最大服務器時間。

咱們還決定隨着時間的推移,客戶端添加多少服務器時間。這是一個經典的leaky bucket 算法。請注意,還有其餘節流算法,但這些算法超出了本章的範圍。在下面的例子中咱們將使用leaky bucket。

讓咱們想象一下,咱們將容許的最大服務器時間(Bucket Size)設置爲1000ms,客戶端每秒得到100ms的服務器時間(Leak Rate),mutation 以下:

mutation {
  createPost(input: { title: "GraphQL Security" }) {
    post {
      title
    }
  }
}

這個mutation平均須要200ms才能完成。實際上,時間可能會有所不一樣,但咱們假設爲了這個例子,它老是須要200ms才能完成。

這意味着在1秒內調用此操做超過5次的客戶端將被阻止,直到更多的可用服務器時間添加到客戶端。

通過兩秒鐘(100ms加秒),咱們的客戶能夠一次調用createPost。

正如你所看到的,基於時間的調節是限制GraphQL查詢不錯的方式,由於複雜的查詢將最終消耗更多的時間,這意味着你不能頻繁地調用它們,而較小的查詢可能被更頻繁地調用,由於它們將很是快速地計算。

但若是GraphQL API是公開的,向客戶端提出這些限制條件就不那麼容易了。在這種狀況下,服務器耗時並不能很好地告知客戶端,客戶端也不能準確的估計他們的查詢所須要的時間,在不先試着請求的狀況下。

還記得咱們以前提到的最大複雜度?若是咱們根據這個調節,會怎麼樣?

基於查詢複雜度的調節

基於查詢複雜度的調節是與客戶端合做的好方法,客戶端能夠遵循schema中的限制。

咱們使用與「查詢複雜性」部分中使用的相同的複雜性示例:

query {
  author(id: "abc") {    # complexity: 1
    posts {              # complexity: 1
      title              # complexity: 1
    }
  }
}

咱們知道這個查詢的成本是基於複雜度的3。就像時間流逝同樣,咱們能夠得知客戶可使用的每次最高成本(Bucket Size)。

若是最大成本爲9,咱們的客戶只能三次運行此查詢,不容許查詢更多。

這些原理與咱們的時間節制相同,但如今將這些限制傳達給客戶端後。客戶甚至能夠本身計算查詢成本,而無需估計服務器時間!

GitHub公共API實際上使用這種方法來扼制客戶端。看看他們如何對用戶表達這些限制:https://developer.github.com/v4/guides/resource-limitations/。

總結

GraphQL很是適合用於客戶端,由於給予了更多的功能。可是,強大的功能也帶來了風險,擔憂客戶端會以很是昂貴的查詢來濫用GraphQL服務器。

有許多方法來保護您的GraphQL服務器免受這些查詢,可是它們都不是萬無一失的。重要的是,咱們要知道有哪些方法可用來限制,並瞭解他們的優缺點,而後採起最優的決定!

相關文章
相關標籤/搜索