GraphQL 验证客户端请求

身份验证是验证用户或进程身份的一个重要的过程。应用程序对用户进行身份验证以确保匿名用户无法获得数据非常重要。在本节中,我们将学习如何对 GraphQL 客户端进行身份验证。


Express JWT

在这个例子中,我们将使用 jQuery 创建一个客户端应用程序。为了验证请求,我们将在服务器端使用 express-jwt模块。

express-jwt 模块是一个中间件,可让您使用 JWT 令牌对 HTTP 请求进行身份验证。JSON Web Token (JWT) 是一个长字符串,用于标识登录用户。

一旦用户成功登录,服务器就会生成一个 JWT 令牌。此标记清楚地标识日志。换句话说,令牌是用户身份的表示。所以下一次,当客户端的请求到达服务器时,它必须出示这个令牌才能获得所需的资源。客户端可以是移动应用程序或 Web 应用程序。

express jwt 模型
express jwt 模型

设置服务器

以下是设置服务器的步骤

一、 下载并安装项目所需的依赖项

创建一个名为apollo-server-app的文件夹。从终端将目录更改为 apollo-server-app。然后,按照开发环境的搭建中说明的步骤 3 到 5 完成下载和安装过程。

二、 创建schema

在项目文件夹 auth-server-app 中添加 schema.graphql 文件并添加以下代码

schema.graphql

type Query
{
   greetingWithAuth:String
}

三、创建解析器

在项目文件夹中创建一个文件resolvers.js,并添加以下代码

解析器将验证经过身份验证的用户对象在 GraphQL 的上下文对象中是否可用。如果经过身份验证的用户不可用,它将引发异常。

resolvers.js

const db = require('./db')

const Query = {
   greetingWithAuth:(root,args,context,info) => {

      //检测 context.user 是否为null
      if (!context.user) {
         throw new Error('Unauthorized');
      }
      return "Hello from Jiyik, welcome back : "+context.user.firstName;
   }
}

module.exports = {Query}

四、创建 Server.js 文件

身份验证中间件使用 JSON Web 令牌对调用者进行身份验证。身份验证的 URL 是 http://localhost:9000/login

这是后期操作。用户必须提交他的电子邮件和密码,这将从后端进行验证。如果使用 jwt.sign 方法生成了有效令牌,则客户端必须将其发送到标头中以用于后续请求。

如果令牌有效,则 req.user 将使用解码后的 JSON 对象设置,以供稍后的中间件用于授权和访问控制。

下面的代码使用 jsonwebtokenexpress-jwt 两个模块来验证请求

  • 当在用户点击 greet 按钮,则请求 /graphql 路由。如果用户未通过身份验证,则会提示他进行身份验证。
  • 用户将看到一个接受电子邮件 ID 和密码的表单。在我们的示例中, /login 路由负责对用户进行身份验证。
  • /login 路由验证是否在数据库中找到了用户提供的凭据的匹配项。
  • 如果凭据无效,则会向用户返回 HTTP 401 异常。
  • 如果凭据有效,服务器会生成一个令牌。此令牌作为响应的一部分发送给用户。这是由 jwt.sign 函数完成的。
const expressJwt = require('express-jwt');
const jwt = require('jsonwebtoken');

//私有key
const jwtSecret = Buffer.from('Zn8Q5tyZ/G1MHltc4F/gTkVJMlrbKiZt', 'base64');

app.post('/login', (req, res) => {
   const {email, password} = req.body;
   
   // 验证数据库
   const user = db.students.list().find((user) =>  user.email === email);
   if (!(user && user.password === password)) {
      res.sendStatus(401);
      return;
   }
   
   //给予私有key生成一个token,该token没有过期时间
   const token = jwt.sign({sub: user.id}, jwtSecret);
   res.send({token});
});

对于每个请求,都会调用 app.use() 函数。这将依次调用 expressJWT 中间件。该中间件将解码 JSON Web 令牌。存储在令牌中的用户 ID 将被检索并存储为请求对象中的属性用户。

// 解码JWT 并将其存储在请求对象中
app.use(expressJwt({
   secret: jwtSecret,
   credentialsRequired: false
}));

为了使 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'}))

//验证
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}`));

五、运行应用程序

创建 server.js 文件并参考开发环境的搭建章节中的步骤 8。下一步是在终端中执行命令 npm start。服务器将在 9000 端口上启动并运行。在这里,我们使用 GraphiQL 作为客户端来测试应用程序。打开浏览器并输入 URL,http://localhost:9000/graphiql

{
   greetingWithAuth
}

在下面的响应中,我们收到一个错误,因为我们不是经过身份验证的用户。

{
  "data": {
    "greetingWithAuth": null
  },
  "errors": [
    {
      "message": "Unauthorized",
      "locations": [
        {
          "line": 2,
          "column": 4
        }
      ],
      "path": [
        "greetingWithAuth"
      ]
    }
  ]
}

GraphQL 验证错误

下面,让我们创建一个客户端应用程序来进行身份验证。

设置 JQuery 客户端

在客户端应用程序中,提供了一个 Greet 按钮,它将调用 greetingWithAuth。如果在未登录的情况下单击该按钮,它将提供如下错误消息

GraphQL 未验证成功的界面

使用数据库中可用的用户登录后,将显示如下消息

GraphQL 验证成功的界面

要访问greeting,我们需要首先访问 URL http://localhost:9000/login 路由,如下所示。

响应将包含从服务器生成的令牌。

$.ajax({
   url:"http://localhost: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 Schema。所有带有 token 的后续请求都应该有一个 Authorizationheader。

{ 
   url: "http://localhost: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: "http://localhost: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:"http://localhost: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 = "mohtashim.mohammad@tutorialpoint.org" 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>

完整演示如下

GraphQL 客户端身份验证

查看笔记