GraphQL與認(rèn)證
很多人會(huì)問GraphQL怎么認(rèn)證和授權(quán)深浮。最終的答案是GraphQL只是一個(gè)查詢語言和認(rèn)證之類的沒什么關(guān)系压怠,每一個(gè)應(yīng)用都可以有自己的實(shí)現(xiàn)方法。但是飞苇,我們還是來深入聊聊這個(gè)問題菌瘫。
大局上看
基本上,有三種情況會(huì)發(fā)生:
- 已經(jīng)登錄的用戶發(fā)出GraphQL查詢布卡,未登錄的用戶不可以雨让。認(rèn)證在非GraphQL節(jié)點(diǎn)完成。
- 所有用戶都可以發(fā)出GraphQL查詢忿等,未登錄用戶可以使用其中的一個(gè)子集栖忠。認(rèn)證在非GraphQL節(jié)點(diǎn)完成。
- 所有用戶都可以發(fā)出GraphQL查詢贸街,認(rèn)證就由GraphQL節(jié)點(diǎn)完成庵寞。
第一種情況可能是普遍存在的。你已經(jīng)有一個(gè)REST或者RPC節(jié)點(diǎn)薛匪,只有認(rèn)證成功的用戶可以訪問捐川,添加/graphql非常的簡單。不好的地方是逸尖,你的客戶端不得不處理GraphQL和非GraphQL兩種情況古沥。這可能是一筆技術(shù)債。
第二種情況是第一種項(xiàng)目進(jìn)化以后的結(jié)果娇跟。最終前端代碼只使用GraphQL岩齿,只不過久經(jīng)考驗(yàn)的認(rèn)證節(jié)點(diǎn)還會(huì)留著。
第三種情況一般是一個(gè)全新的后端會(huì)有的形態(tài)逞频,盡量避免處理非GraphQL節(jié)點(diǎn)纯衍。我(作者)還沒有見過這樣形態(tài)的服務(wù)端。
非GraphQL節(jié)點(diǎn)的處理機(jī)制
非GraphQL節(jié)點(diǎn)苗胀,我是指cookies襟诸、JSON和web tokens,或者HTTP基本認(rèn)證基协「枨祝基本上無論何種方式,你的server都可以通過認(rèn)證一個(gè)用戶澜驮、一個(gè)請(qǐng)求并最終把數(shù)據(jù)傳輸給你的resolver陷揪。
這里是一個(gè)使用express-graphql
和cookies是例子(從他們的例子里結(jié)果來的):
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里,請(qǐng)求在一個(gè)比GraphQL路由更早的中間件處理了。之后悍缠,請(qǐng)求才會(huì)到達(dá)GraphQL代碼卦绣。我們知道請(qǐng)求是從哪里來的。我們甚至都可以在請(qǐng)求到達(dá)GraphQL代碼以前飞蚓,把請(qǐng)求重定向到登錄頁面滤港。
下面的例子使用了express-session
,但是處理的原則和express-jwt
差不多趴拧。在GraphQL層面上溅漾,你的schema代碼會(huì)是這樣的:
new GraphQLObjectType({
name: 'Secrets',
fields: {
bigSecret: {
type: GraphQLString,
resolve(parentValue, _, { rootValue: { session } }) {
return getBigSecret(session);
}
}
}
});
只要能取到session,那么用戶就可以訪問其他相關(guān)的資源了著榴√砺模或者,如果session不存在脑又,那么你按照你的設(shè)計(jì)拋出錯(cuò)誤或者實(shí)現(xiàn)其他的處理暮胧。
關(guān)鍵是rootValue
并沒有在我們的GraphQL模式中定義為一個(gè)公開的字段或者參數(shù),我們不信任客戶端直接發(fā)送過來的數(shù)據(jù)问麸,所以它是由server的其他代碼注入的叔壤。
使用GraphQL時(shí)的實(shí)現(xiàn)機(jī)制
但是我們要完全的使用GraphQL呢?以上的方法可以在使用了express-graphql
的時(shí)候使用口叙。但是無法遷移到其他的實(shí)現(xiàn)里。
在少數(shù)的例子里嗅战,F(xiàn)acebook談到了 concept of a viewer field妄田。主要的思想是你的應(yīng)用的數(shù)據(jù)和誰訪問相關(guān),所以全部的其他字段的數(shù)據(jù)都嵌入到里面驮捍。實(shí)際情況是疟呐,不可能所有的數(shù)據(jù)都和訪問者相關(guān)。但是這么做的話东且,你可以有改變的余地启具。
{
viewer {
name
friends {
name
}
getProfile(id: String!) {
name
}
}
}
注意即使和訪問者無關(guān)的getProfile
字段也放在了viewer
字段里,為了以防萬一哪天要限制訪問者可以訪問的數(shù)據(jù)的時(shí)候處理起來就簡單了珊泳。
一個(gè)像Facebook一樣的APP為了保護(hù)隱私鲁冯,有很多什么人可以查看什么數(shù)據(jù)的邏輯處理。即使是一個(gè)簡單的APP也不會(huì)讓用戶查看他沒有創(chuàng)建的數(shù)據(jù)色查。一個(gè)常用的方法是修改URL里的userID來查看一些私有數(shù)據(jù)薯演,如果server不檢查用戶所有權(quán)的話。使用一個(gè)單一的viewer
字段就讓所有權(quán)檢查簡單了很多秧了。
上面的schema
也可以和非GraphQL節(jié)點(diǎn)的認(rèn)證方法一起使用跨扮。但是如果我們這么干的話呢:
{
viewer(token: String) {
name
}
}
如果不是用header或者查詢參數(shù)(比如:JWT、OAuth、等)衡创,我們可以把它放在GraphQL的查詢里帝嗡。你的schema
的代碼可以使用JWT庫等工具直接解析傳過來的token。
**注意**:永遠(yuǎn)使用HTTPS來傳輸敏感數(shù)據(jù)璃氢。
要發(fā)出新的token哟玷,mutation就可以使用了:
mutation {
createToken(username: String!, password: String!) {
token
error
}
}
我們可以認(rèn)證放在mutation里,要么返回一個(gè)token
要么返回一個(gè)錯(cuò)誤拔莱。這樣前端就可以把token存起來在之后的請(qǐng)求里使用了碗降。
譯自:https://medium.com/the-graphqlhub/graphql-and-authentication-b73aed34bbeb#.8sif1n1lj