
Node.js 后端開發指南:搭建、優化與部署
在左邊,我們有不同類型的中間件,它們將一些邏輯應用于我們所有的請求。
中間部分是GraphQL 引擎,它利用 GraphQL Schema 確定如何將 GraphQL 查詢轉換為對正確解析器函數的調用。在大多數框架中,例如 Node.js 的 Express 框架,GraphQL 引擎也被實現為中間件。
在右側,我們有不同類型的解析器函數,它們將 GraphQL 引擎與我們的業務邏輯連接起來。當查詢相應的數據類型時,GraphQL 引擎會執行解析器。
業務邏輯可以是一個整體系統或一組微服務。
要授權請求,我們需要在業務邏輯中訪問當前用戶的身份。這要求我們將身份驗證邏輯放到一個對所有請求都執行的位置。如果我們查看上面的圖表,就會發現我們系統的中間件部分是執行該邏輯的最佳位置。
這種方法的另一個優點是:它完全獨立于 GraphQL,因此我們可以重新使用我們的 REST 技能并重新應用它們!
為了理解這一點,讓我們看一個中間件、REST 資源處理程序函數和 GraphQL 解析器函數的簡單實現。在這個例子中,我將使用 Node.js、Express 框架和 Apollo Server。
首先,我們的業務邏輯:
const getMessage = (id, user) => {
const message = dataStore[id];
if (message.recipient.id === user.id) return message;
if (message.sender.id === user.id) return message;
throw new Error("no permission");
};
它根據 ID 從內存存儲中加載消息。如果用戶是此消息的收件人或發件人,則允許他們閱讀該消息。否則,我們會拋出錯誤。
二、認證中間件:
const authMiddleware = (request, response, next) => {
const token = request.get("authorization");
const user = loadUser(token);
if (user) {
request.user = user;
next();
}
response.status(401).end();
};
它從請求標頭加載令牌并使用它來加載當前用戶。如果成功,它會將此用戶對象添加到請求中,以便稍后使用。
現在,讓我們看一下 REST 實現。
expressApp.use(authMiddleware);
expressApp.get("/message/:id", (request, response) => {
try {
const message = getMessage(request.params.id, request.user);
response.end(message);
} catch (e) {
response.status(403).end({ error: e.message });
}
});
首先,我們使用我們的authMiddleware
;這確保我們的端點處理程序中request
有一個user
對象。
然后,我們定義一個函數,用于向/messages/:id
端點發送 GET 請求。它使用id
參數和user
我們的對象request
來加載message
業務邏輯函數。
如果一切順利,我們將獲得message
并可以將其發送給客戶端。如果不順利,我們將 HTTP 狀態設置為403
(禁止)。
該getMessage
函數借助user
對象來處理授權。它不關心它來自哪里。
接下來讓我們看一個 GraphQL 實現,這里是用Apollo Server創建的。
expressApp.use(authMiddleware);
const { ApolloServer, gql } = require("apollo-server-express");
const typeDefs = gql`
type Query {
message(id: String!): String
}
`;
const resolvers = {
Query: {
message: (parent, args, request, info) =>
getMessage(args.id, request.user)
}
};
const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app: expressApp });
中間件的使用在這里保持不變。因此無需進行任何身份驗證更改。
然后,我們使用該apollo-server-express
模塊定義一個僅具有Query
類型的簡單 GraphQL API message
。
然后我們定義解析器函數,它看起來與 REST 示例中的幾乎相同。
解析器有四個參數,第三個參數是request
我們已經從 REST 實現中知道的。由于我們的中間件使用此對象來附加user
對象,因此我們可以像以前一樣將其傳遞給我們的業務邏輯。
GraphQL 引擎id
從我們的查詢中解析參數并將其存儲到第二個參數中,因此只有它的位置不同。
我們的業務邏輯功能處理授權,與 REST 實現相同。
這里的區別在于GraphQL 不了解 HTTP或其狀態代碼。如果它可以處理查詢(即使結果為空),它也會直接使用200
。
我們的業務邏輯拋出的錯誤將被放置在errors
響應主體中的數組內,并且客戶端可以以某種方式處理它。
我們的響應message
JSON 將被設置為null
,這使我們能夠使用部分數據響應客戶端的請求。例如,如果消息位于 GraphQL 響應樹的某個深處。
{
"data": {
"message": null
},
"errors": [
{
"message": "no permission",
...
}
]
}
既然我們已經討論了 API 中的身份驗證和授權,我們還應該看看客戶端的情況。
如果我們采用目前最流行的基于 token 的身份驗證,那么首先需要獲取這樣的 token。我們可以通過兩種方式來實現。
Auth 令牌檢索
額外的 HTTP 端點將整個身份驗證過程與 GraphQL API 分離。就像服務器端的中間件方法一樣。如果執行身份驗證的服務與我們的 API 不同,并且我們只需要來自它的令牌,那么這尤其有用。
GraphQL 突變感覺更加集成,但需要我們的 API 中有更多特殊情況,因為我們需要讓用戶以某種方式訪問??它們而無需經過身份驗證。
我認為第一種方式風險較小。我們可以專注于 GraphQL API 的核心競爭力,并為未來保持靈活性。
代幣存儲和放置
令牌需要與我們的 GraphQL 請求一起發送到我們的 API。
為此,我們需要決定在哪里存儲令牌以及在請求的哪里放置它。
存儲的方式有兩種,localStorage
HTTP?cookies。
當存儲在 中時localStorage
,我們可以通過 HTTP 標頭發送令牌。當我們將其存儲在 cookie 中時,我們會自動將其與 cookie 一起發送。
它與 REST API 相同,兩種變體各有利弊。我不會詳細介紹,但您可以在我們的REST 身份驗證文章中閱讀相關內容。
使用 Apollo 客戶端的示例
讓我們看一個簡單的示例,即 Apollo Client,它是一個用 JavaScript 編寫的獨立于 UI 框架的 GraphQL 客戶端庫。
import { ApolloClient } from "apollo-client";
import { HttpLink } from "apollo-link-http";
import { ApolloLink, concat } from "apollo-link";
const httpLink = new HttpLink({ uri: "/graphql" });
const authMiddleware = new ApolloLink((operation, forward) => {
operation.setContext({
headers: {
authorization: localStorage.getItem("token") || null
}
});
return forward(operation);
});
const client = new ApolloClient({
link: concat(authMiddleware, httpLink)
});
來源:Apollo 客戶端文檔
這里我們使用localStorage
/header 變體來存儲和發送令牌。
Apollo 客戶端有一個名為Apollo Link的網絡層。它用于使不同的協議可插入。正如我們在 Express 示例的后端所看到的那樣,鏈接被實現為中間件。
在此示例中,我們將 HTTP 中間件配置為我們的 GraphQL API 端點,然后創建一個自定義中間件/鏈接用于身份驗證。
兩者都插入ApolloClient
構造函數,因此它們會執行我們發送的所有 GraphQL 查詢。
自定義在每次請求之前authMiddleware
進行檢查,并將HTTP 標頭設置為其找到的令牌。localStorage
authorization
到這里,我們已經看到了將身份驗證保留在 GraphQL 之外的優點。我們可以fetch
對 HTTP 端點進行簡單調用以進行注冊或登錄,并在開始創建 GraphQL 客戶端對象之前將收到的令牌存儲在某個地方。
反過來也是一樣。如果我們想注銷用戶,我們可以丟棄 GraphQL 客戶端對象,而不需要以某種方式改變它。
如果我們正確設置了身份驗證,API 現在就知道我們是我們所說的那個人,但仍然有可能我們想要查詢我們無權訪問的數據。
在客戶端,就像在后端一樣,授權是業務邏輯,因此我們需要考慮在具體情況下該怎么做。
我們有一個 UI,發送一個大的 GraphQL 查詢來一次性獲取所有數據,現在它返回了一些字段null
和一些錯誤。
Apollo Client庫為此定義了三種錯誤策略,我們可以為每個或所有請求設置其中一種。
fetch
這些策略定義了我們可以在自定義客戶端中或在無需任何特定 GraphQL 庫的情況下直接使用的行為。
身份驗證和授權是 API 設計中非常重要的主題,但正如本文所示,一旦我們了解了 HTTP API 身份驗證的基礎知識,我們就可以在 REST 和 GraphQL 上重復使用我們的技能,而無需進行太多改變。
了解授權是業務邏輯,并且通常完全針對特定軟件產品進行定制,這一點也很重要。這意味著它應該被推到我們代碼庫的邊緣,這樣它就很容易被找到,并且不會在許多地方重復。
文章來源:Steps to building Authentication and Authorization for GraphQL APIs