- GraphQL 教程
- GraphQL - 首页
- GraphQL - 简介
- GraphQL - 环境搭建
- GraphQL - 架构
- GraphQL - 应用组件
- GraphQL - 示例
- GraphQL - 类型系统
- GraphQL - 模式 (Schema)
- GraphQL - 解析器 (Resolver)
- GraphQL - 查询 (Query)
- GraphQL - 突变 (Mutation)
- GraphQL - 验证
- GraphQL - JQuery 集成
- GraphQL - React 集成
- GraphQL - Apollo Client
- GraphQL - 客户端认证
- GraphQL - 缓存
- GraphQL 有用资源
- GraphQL - 快速指南
- GraphQL - 有用资源
- GraphQL - 讨论
GraphQL - 客户端认证
身份验证是验证用户或进程身份的过程或行为。应用程序对用户进行身份验证非常重要,以确保匿名用户无法访问数据。在本节中,我们将学习如何对 GraphQL 客户端进行身份验证。
Express JWT
在这个例子中,我们将使用 jQuery 创建一个客户端应用程序。为了验证请求,我们将在服务器端使用 **express-jwt** 模块。
**express-jwt** 模块是一个中间件,允许您使用 JWT 令牌对 HTTP 请求进行身份验证。JSON Web 令牌 (JWT) 是一长串字符串,用于标识已登录的用户。
用户成功登录后,服务器会生成一个 JWT 令牌。此令牌唯一标识一个登录。换句话说,令牌是用户身份的表示。因此,下次客户端访问服务器时,必须出示此令牌才能获取所需的资源。客户端可以是移动应用程序或 Web 应用程序。
图示
我们将按照分步骤的过程来理解此图示。
设置服务器
以下是设置服务器的步骤:
步骤 1 - 下载并安装项目所需的依赖项
创建一个文件夹 **auth-server-app**。从终端将您的目录更改为 **auth-server-app**。按照“环境搭建”章节中步骤 3 到 5 的说明进行操作。
步骤 2 - 创建模式 (Schema)
在项目文件夹 **auth-server-app** 中添加 **schema.graphql** 文件,并添加以下代码:type Query { greetingWithAuth:String }
步骤 3 - 添加解析器 (Resolvers)
在项目文件夹中创建一个文件 **resolvers.js**,并添加以下代码:
解析器将验证 GraphQL 上下文对象中是否存在经过身份验证的用户对象。如果不存在经过身份验证的用户,它将引发异常。
const db = require('./db') const Query = { greetingWithAuth:(root,args,context,info) => { //check if the context.user is null if (!context.user) { throw new Error('Unauthorized'); } return "Hello from TutorialsPoint, welcome back : "+context.user.firstName; } } module.exports = {Query}
步骤 4 - 创建 Server.js 文件
身份验证中间件使用 JSON Web 令牌对调用者进行身份验证。身份验证的 URL 为 **https://127.0.0.1:9000/login**。
这是一个 POST 操作。用户必须提交其电子邮件和密码,这些信息将从后端进行验证。如果使用 `jwt.sign` 方法生成了有效的令牌,则客户端必须在后续请求的标头中发送此令牌。
如果令牌有效,则 `req.user` 将设置为已解码的 JSON 对象,供后续中间件用于授权和访问控制。
以下代码使用了两个模块:`jsonwebtoken` 和 `express-jwt` 来验证请求:
当用户单击“问候”按钮时,将发出对 /graphql 路由的请求。如果用户未经身份验证,系统将提示他进行身份验证。
用户将看到一个接受电子邮件 ID 和密码的表单。在我们的示例中,/login 路由负责对用户进行身份验证。
/login 路由验证数据库中是否存在与用户提供的凭据匹配的条目。
如果凭据无效,则会向用户返回 HTTP 401 异常。
如果凭据有效,服务器将生成一个令牌。此令牌作为响应的一部分发送给用户。这是由 `jwt.sign` 函数完成的。
const expressJwt = require('express-jwt'); const jwt = require('jsonwebtoken'); //private key const jwtSecret = Buffer.from('Zn8Q5tyZ/G1MHltc4F/gTkVJMlrbKiZt', 'base64'); app.post('/login', (req, res) => { const {email, password} = req.body; //check database const user = db.students.list().find((user) => user.email === email); if (!(user && user.password === password)) { res.sendStatus(401); return; } //generate a token based on private key, token doesn't have an expiry const token = jwt.sign({sub: user.id}, jwtSecret); res.send({token}); });
对于每个请求,都将调用 `app.use()` 函数。这将依次调用 expressJWT 中间件。此中间件将解码 JSON Web 令牌。存储在令牌中的用户 ID 将被检索并存储为请求对象中的 `user` 属性。
//decodes the JWT and stores in request object app.use(expressJwt({ secret: jwtSecret, credentialsRequired: false }));
为了在 GraphQL 上下文中使用 `user` 属性,此属性将分配给 **context** 对象,如下所示:
//Make req.user available to GraphQL context app.use('/graphql', graphqlExpress((req) => ({ schema, context: {user: req.user &&apm; db.students.get(req.user.sub)} })));
在当前文件夹路径中创建 **server.js**。完整的 server.js 文件如下:
const bodyParser = require('body-parser'); const cors = require('cors'); const express = require('express'); const expressJwt = require('express-jwt'); //auth const jwt = require('jsonwebtoken'); //auth const db = require('./db'); var port = process.env.PORT || 9000 const jwtSecret = Buffer.from('Zn8Q5tyZ/G1MHltc4F/gTkVJMlrbKiZt', 'base64'); const app = express(); const fs = require('fs') const typeDefs = fs.readFileSync('./schema.graphql',{encoding:'utf-8'}) const resolvers = require('./resolvers') const {makeExecutableSchema} = require('graphql-tools') const schema = makeExecutableSchema({typeDefs, resolvers}) app.use(cors(), bodyParser.json(), expressJwt({ secret: jwtSecret, credentialsRequired: false })); const {graphiqlExpress,graphqlExpress} = require('apollo-server-express') app.use('/graphql', graphqlExpress((req) => ({ schema, context: {user: req.user && db.students.get(req.user.sub)} }))); app.use('/graphiql',graphiqlExpress({endpointURL:'/graphql'})) //authenticate students app.post('/login', (req, res) => { const email = req.body.email; const password = req.body.password; const user = db.students.list().find((user) => user.email === email); if (!(user && user.password === password)) { res.sendStatus(401); return; } const token = jwt.sign({sub: user.id}, jwtSecret); res.send({token}); }); app.listen(port, () => console.info(`Server started on port ${port}`));
步骤 5 - 运行应用程序
在终端中执行命令 `npm start`。服务器将在 9000 端口启动并运行。在这里,我们使用 GraphiQL 作为客户端来测试应用程序。
打开浏览器并输入 URL **https://127.0.0.1:9000/graphiql**。在编辑器中键入以下查询:
{ greetingWithAuth }
在下面的响应中,我们收到一个错误,因为我们不是经过身份验证的用户。
{ "data": { "greetingWithAuth": null }, "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "greetingWithAuth" ] } ] }
在下一节中,让我们创建一个客户端应用程序进行身份验证。
设置 JQuery 客户端
在客户端应用程序中,提供了一个问候按钮,它将调用模式 **greetingWithAuth**。如果您在登录之前单击该按钮,您将收到如下错误消息:
登录数据库中可用的用户后,将出现以下屏幕:
要访问 **greeting**,我们首先需要访问 URL **https://127.0.0.1:9000/login** 路由,如下所示。
响应将包含从服务器生成的令牌。
$.ajax({ url:"https://127.0.0.1:9000/login", contentType:"application/json", type:"POST", data:JSON.stringify({email,password}), success:function(response) { loginToken = response.token; $('#authStatus') .html("authenticated successfully") .css({"color":"green",'font-weight':'bold'}); $("#greetingDiv").html('').css({'color':''}); }, error:(xhr,err) => alert('error') })
成功登录后,我们可以访问 `greetingWithAuth` 模式,如下所示。对于所有后续请求,都应包含一个 `Authorization` 标头,其中包含 `bearer` 令牌。
{ url: "https://127.0.0.1:9000/graphql", contentType: "application/json", headers: {"Authorization": 'bearer '+loginToken}, type:'POST', data: JSON.stringify({ query:`{greetingWithAuth}` }
以下是 index.html 的代码:
<!DOCTYPE html> <html> <head> <script src = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script> $(document).ready(function() { let loginToken = ""; $("#btnGreet").click(function() { $.ajax({url: "https://127.0.0.1:9000/graphql", contentType: "application/json", headers: {"Authorization": 'bearer '+loginToken}, type:'POST', data: JSON.stringify({ query:`{greetingWithAuth}` }), success: function(result) { $("#greetingDiv").html("<h1>"+result.data.greetingWithAuth+"</h1>") }, error:function(jQxhr,error) { if(jQxhr.status == 401) { $("#greetingDiv").html('please authenticate first!!') .css({"color":"red",'font-weight':'bold'}) return; } $("#greetingDiv").html('error').css("color","red"); } }); }); $('#btnAuthenticate').click(function() { var email = $("#txtEmail").val(); var password = $("#txtPwd").val(); if(email && password) { $.ajax({ url:"https://127.0.0.1:9000/login", contentType:"application/json", type:"POST", data:JSON.stringify({email,password}), success:function(response) { loginToken = response.token; $('#authStatus') .html("authenticated successfully") .css({"color":"green",'font-weight':'bold'}); $("#greetingDiv").html('').css({'color':''}); }, error:(xhr,err) => alert('error') }) }else alert("email and pwd empty") }) }); </script> </head> <body> <h1> GraphQL Authentication </h1> <hr/> <section> <button id = "btnGreet">Greet</button> <br/> <br/> <div id = "greetingDiv"></div> </section> <br/> <br/> <br/> <hr/> <section id = "LoginSection"> <header> <h2>*Login first to access greeting </h2> </header> <input type = "text" value = "[email protected]" placeholder = "enter email" id = "txtEmail"/> <br/> <input type = "password" value = "pass123" placeholder = "enter password" id = "txtPwd"/> <br/> <input type = "button" id = "btnAuthenticate" value = "Login"/> <p id = "authStatus"></p> </section> </body> </html>