JSON Web Token认证的操作指南

image

在本文中,我们将了解JSON Web Token的全部内容。

我们将从JWT的基本概念开始,然后查看其结构,最后创建一个简单的服务器,它将获取一些数据并将其插入到JWT中。

什么是JSON Web Token?为什么我们需要它?

JSON Web Token(JWT)是一种安全,紧凑且独立的方式,以JSON对象的形式在多方之间传输信息。

假设你想登录一个应用程序,比如说 Tinder。 Tinder允许用户使用他们的Facebook个人资料登录。 因此,当用户选择使用Facebook登录的选项时,该应用程序会使用用户的凭据(用户名和密码)联系Facebook的身份验证服务器。

验证服务器验证用户的凭据后,将创建JWT并将其发送给用户。 该应用程序现在获得此JWT并允许用户访问其数据。

jwt的结构

一个jwt是由三个以"."来区分开的部分来组成的,他们是:

  • 头部
  • 负载
  • 签名

标头通常由两部分组成:令牌的类型和正在使用的散列算法。

{
  "alg": "HS256",
  "typ": "JWT"
}

负载是我们要发送的实际信息的存储位置。 以下是简单有效负载的示例。 要知道负载可能比这个示例更复杂,以确保更好的安全性。

{
  "sub": "65165751325",
  "name": "Rajat S",
  "admin": true
}

签名用于验证消息在到达目的地之前没有被更改。 这通常通过使用私钥来实现。

这三个部分通常编码为三个在他们之间由.分隔的Base64-URI字符串

要轻松解码,验证,生成或只是使用JWT,请查看Auth0的JWT.IO调试器

现在我们已经基本了解了JSON是什么,让我们来看看如何构建一个简单的身份验证服务器来发布JWT,然后我们将使用它来访问API。

提示:在JavaScript中编写可重用代码时,可以使用Bit快速将其转换为共享组件,以便在不同位置使用,开发和同步。 试试吧。

让我们开始吧

在开始之前, 让我们构建一个简单的web服务端,首先用npm下载express到你的电脑当中,然后打开命令行工具,执行以下语句

$ npm install express

然后,新建一个文件夹叫做 jwt-auth. 当然你也可以命名为其他名字

$ mkdir jwt-auth
$ cd jwt-auth

在这个文件夹里新建一个文件叫做index.js . 这个文件是用来写我们的代码并且启动一个web服务器,并且包含一个独立的路由能够显示当前日期和时间还有一个404页面的处理程序,

打开一个编辑器,写如下的代码

const express = require("express");
const app = express();
const PORT = 8888;
app.get('/time', (req, res) => {
  const time = (new Date()).toLocaleTimeString();
  res.status(200).send(`The Time is ${time}`);
});

在这里,我们首先导入我们刚刚安装的express库。 然后我正在创建一个名为appconst,它将使express库。 我还创建了另一个名为PORT的常量,它将包含我们的服务器将运行的端口号。 我选择了8888作为端口号。 如果您愿意,也可以使用任何其他端口号。

接下来,我创建了一个名为time的新路由。 此路由以响应请求作为参数进行回调。 简而言之,此路由将向用户显示当前本地时间。

当想要服务器把我们带到除了time之外的任何路由时,我们还要添加一个路由。 这被称为catchall路由。 我希望服务器在此路由上返回404错误。 要实现此功能,请将以下代码写入index.js文件:

app.get("*", (req, res) => {
  res.sendStatus(404);
});

还有最后一件事要做。 我们没有告诉我们的服务器要监听哪个端口。 我们已将端口号初始化为8888.但我们还没有在代码中实际使用此定义。 为此,请使用app.listen方法,如下所示:

app.listen(PORT, () => {     
  console.log(`Server is running on port ${PORT}.`); 
});

完成后,我们可以通过命令行输入node来启动服务器。 然后,转到localhost:8888,您会发现它显示Not Found消息。 添加/时间到URL,它会显示本地时间,如下所示:

image

最终我们已经建立了一个express服务器,它将向我们在/time路由显示当地时间以及任何其他路由上的404错误。

**添加一个登陆路由
由于本文是关于JWT身份验证的,因此我们实现一个名为login的路由。 与之前使用getlisten的部分中的路由不同,此路由将使用post创建。

app.post("/login", (req, res) => {
  const user = req.body.username;
  res.status(200).send(`User's name is ${user}`);
})

但目前我们的服务器无法读取请求的正文。 为了解决这个问题,我们将安装一个名为body-parser的新中间件。

$ npm install body-parser

然后就像我们使用express库一样,使用require语法将其导入index.js文件。

const bodyParser = require("body-parser");

我们现在可以告诉express使用body-parser来处理所有JSON响应,如下所示:

app.use(bodyParser.json());

现在我们通过node.来启动服务器

为了测试这个新的路由,我们需要用到

POSTMAN

,如果你还没有安装的话,下载一下这个吧。

在POSTMAN中,我们创建一个post请求到这个登陆路由,如下所示:

image

在这个请求体内,创建一个如下的json:

image

点击发送应该得到以下响应:

image

发布JWT
现在我们已经构建了一个可以处理GET和POST请求的简单服务器,让我们构建一个简单的JWT发布。

让我们首先创建一个名为users的新数组,其中包含几个用户及其密码,如下所示:

const users = [
  {id: 1, username: "clarkKent", password: "superman"},
  {id: 2, username: "bruceWayne", password: "batman"}
];

让我们重写 login 路由,首先我们需要检查提交上来的请求里面是否同时包含username 和 password,如果不是的话,那么服务端需要返回400状态码。

app.post("/login", (req, res) => {
  if (!req.body.username || !req.body.password) {
    res.status(400).send("Error. Please enter the correct username and password");
    return;
  }

然后如果这个请求有效,我们需要检查是否用户名在我们的users数组里面,
在if语句下面的相同的login路由中写:

const user = users.find((u) => {
  return u.username === req.body.username && u.password === req.body.password;
});

如果这个用户并未在我们的users数组当中,我们需要返回401的状态码,同样是在/login路由中:

在继续进行之前,我们下载另一个库叫做 jsonwebtoken

$ npm install jsonwebtoken

在index.js文件的头部,用require语句引入jsonwebtoken

const jwt = require("jsonwebtoken");

我们将使用此库为每个有效用户创建JSON Web令牌。 为此,我们将使用jsonwebtoken库中的sign方法创建一个名为token的新const。 请注意,我们仍然在/ login路由中编写代码。

const token = jwt.sign({
  sub: user.id,
  username: user.username
}, "mykey", {expiresIn: "3 hours"});
res.status(200).send({access_token: token})

现在去到POSTMAN,发送一个post请求,请求中带有如下的json结构

{
  "username": "clarkKent",
  "password": "superman",
}

运行结果会是如下的一个JSON Web Token

image

如果你复制这个access_token到 JWT.IO Debugger中,你会看到相同的用户名和密码,只不过多了一些其他的消息,

image

旁注 - CORS

在某些情况下,您的应用尝试访问的API运行在与应用程序不同的服务器上,将返回某种“无法加载”错误。

为了防止这种情况发生,我们可以安装另一个名为cors的库

$ npm install cors

在index.js头部,用require语句引入这个库

const cors = require("cors");

然后类似于我们对body-parser库所做的,我们将告诉express使用cors,如下所示:

app.use(cors());

使用JWT为API提供用户访问权限

在这里,我们将创建一个包含两个路由的新API。 第一个路由将是公共的,而第二个将要求用户使用JWT进行身份验证以便访问它。

让我们在jwt-auth文件夹中创建一个名为api.js的新文件。 在此文件中,编写以下启动代码:

const express = require("express");
const bodyParser = require("body-parser"); 
const app = express();
const PORT = process.env.API_PORT || 8888;
app.use(bodyParser.json());   
app.get("*", (req, res) => {    
  res.sendStatus(404);
}); 
app.listen(PORT, () => {    
  console.log(`Server is running on port ${PORT}.`);
});

让我们创建一个路由名叫 '/asset' 这个是公共路由,将会直接返回一个200的状态码

app.get("/asset", (req, res) => {
  res.status(200).send("Everybody can see this");
});

第二个秘密路由是/asset/secret,它将使用jwtCheck进行保护,如下所示:

app.get("/asset/secret", jwtCheck, (req, res) => {
  res.status(200).send("Only logged in people can see me");
});

因为index.js文件已经设置启动在8888端口,我们需要把这个文件设置在其他端口,为了实现,我们需要打开命令行工具,然后运行如下代码:

$ export API_PORT=5555

尽管我们已经说过/ asset / secret路由是安全的,但我们还没有真正实现它。 为此,我们需要安装另一个名为express-jwt的中间件库。

$ npm install express-jwt

在index.js头部,用require语句引入这个库

const expressjwt = require("express-jwt");

我们将使用此库来定义jwtCheck。 我们将使用jwtCheck来检查签名是否与我们从身份验证服务器获得的签名相匹配。 如果你还记得,我们把它命名为“mykey”。

const jwtCheck = expressjwt({    
  secret: "mykey"
});

运行node api.js命令, 检查这个权限的运行情况是否正常, 去POSTMAN发送get请求到localhost:5000/asset,他的响应应该是Everybody can see this

然后你发送请求到localhost:5000/asset/secret ,你应该会看到一个大的错误提示:

image

要解决此问题,请转到POSTMAN中的Authentication选项卡,然后选择Type as Bearer Token。 然后输入Token的值为我们在前面部分中算出的值。

结论

与其他Web令牌(如简单Web令牌(SWT)或安全断言标记语言(SAML))相比,JWT更简单,因为它基于JSON,比XML更容易理解。

如果我们对JSON进行编码,它的大小将比SAML更小,从而更容易传入HTML和HTTP环境。

安全方面,SWT使用单个密钥,而JWT和SAML都使用公钥和私钥对进行更好的身份验证。

从使用的角度来看,JWT用于互联网规模。这意味着在用户的设备上处理更容易,无论是笔记本电脑还是移动设备。

除了身份验证之外,JSON Web令牌是一种在多方之间传输数据的绝佳方式。 JWT具有签名这一事实使每个人都可以更轻松地识别信息发送者。您只需要正确的密钥即可