GraphQL與認證
很多人會問GraphQL怎么認證和授權。最終的答案是GraphQL只是一個查詢語言和認證之類的沒什么關系,每一個應用都可以有自己的實現方法。但是,我們還是來深入聊聊這個問題。
大局上看
基本上,有三種情況會發生:
- 已經登錄的用戶發出GraphQL查詢,未登錄的用戶不可以。認證在非GraphQL節點完成。
- 所有用戶都可以發出GraphQL查詢,未登錄用戶可以使用其中的一個子集。認證在非GraphQL節點完成。
- 所有用戶都可以發出GraphQL查詢,認證就由GraphQL節點完成。
第一種情況可能是普遍存在的。你已經有一個REST或者RPC節點,只有認證成功的用戶可以訪問,添加/graphql非常的簡單。不好的地方是,你的客戶端不得不處理GraphQL和非GraphQL兩種情況。這可能是一筆技術債。
第二種情況是第一種項目進化以后的結果。最終前端代碼只使用GraphQL,只不過久經考驗的認證節點還會留著。
第三種情況一般是一個全新的后端會有的形態,盡量避免處理非GraphQL節點。我(作者)還沒有見過這樣形態的服務端。
非GraphQL節點的處理機制
非GraphQL節點,我是指cookies、JSON和web tokens,或者HTTP基本認證。基本上無論何種方式,你的server都可以通過認證一個用戶、一個請求并最終把數據傳輸給你的resolver。
這里是一個使用express-graphql
和cookies是例子(從他們的例子里結果來的):
var session = require('express-session');
var graphqlHTTP = require('express-graphql');
var MySchema = require('./MySchema');
var app = express();
app.use(session({ secret: 'secret', cookie: { maxAge: 60000 }}));
app.use('/graphql', graphqlHTTP((request) => ({
schema: MySchema,
rootValue: { session: request.session },
graphiql: truem
})));
在express里,請求在一個比GraphQL路由更早的中間件處理了。之后,請求才會到達GraphQL代碼。我們知道請求是從哪里來的。我們甚至都可以在請求到達GraphQL代碼以前,把請求重定向到登錄頁面。
下面的例子使用了express-session
,但是處理的原則和express-jwt
差不多。在GraphQL層面上,你的schema代碼會是這樣的:
new GraphQLObjectType({
name: 'Secrets',
fields: {
bigSecret: {
type: GraphQLString,
resolve(parentValue, _, { rootValue: { session } }) {
return getBigSecret(session);
}
}
}
});
只要能取到session,那么用戶就可以訪問其他相關的資源了。或者,如果session不存在,那么你按照你的設計拋出錯誤或者實現其他的處理。
關鍵是rootValue
并沒有在我們的GraphQL模式中定義為一個公開的字段或者參數,我們不信任客戶端直接發送過來的數據,所以它是由server的其他代碼注入的。
使用GraphQL時的實現機制
但是我們要完全的使用GraphQL呢?以上的方法可以在使用了express-graphql
的時候使用。但是無法遷移到其他的實現里。
在少數的例子里,Facebook談到了 concept of a viewer field。主要的思想是你的應用的數據和誰訪問相關,所以全部的其他字段的數據都嵌入到里面。實際情況是,不可能所有的數據都和訪問者相關。但是這么做的話,你可以有改變的余地。
{
viewer {
name
friends {
name
}
getProfile(id: String!) {
name
}
}
}
注意即使和訪問者無關的getProfile
字段也放在了viewer
字段里,為了以防萬一哪天要限制訪問者可以訪問的數據的時候處理起來就簡單了。
一個像Facebook一樣的APP為了保護隱私,有很多什么人可以查看什么數據的邏輯處理。即使是一個簡單的APP也不會讓用戶查看他沒有創建的數據。一個常用的方法是修改URL里的userID來查看一些私有數據,如果server不檢查用戶所有權的話。使用一個單一的viewer
字段就讓所有權檢查簡單了很多。
上面的schema
也可以和非GraphQL節點的認證方法一起使用。但是如果我們這么干的話呢:
{
viewer(token: String) {
name
}
}
如果不是用header或者查詢參數(比如:JWT、OAuth、等),我們可以把它放在GraphQL的查詢里。你的schema
的代碼可以使用JWT庫等工具直接解析傳過來的token。
**注意**:永遠使用HTTPS來傳輸敏感數據。
要發出新的token,mutation就可以使用了:
mutation {
createToken(username: String!, password: String!) {
token
error
}
}
我們可以認證放在mutation里,要么返回一個token
要么返回一個錯誤。這樣前端就可以把token存起來在之后的請求里使用了。
譯自:https://medium.com/the-graphqlhub/graphql-and-authentication-b73aed34bbeb#.8sif1n1lj