理解 JSON Web Token(JWT) 验证
JSON Web Token认证的操作指南
在本文中,我们将了解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
库。 然后我正在创建一个名为app
的const
,它将使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,它会显示本地时间,如下所示:
最终我们已经建立了一个express服务器,它将向我们在/time
路由显示当地时间以及任何其他路由上的404错误。
**添加一个登陆路由
由于本文是关于JWT身份验证的,因此我们实现一个名为login的路由。 与之前使用get
和listen
的部分中的路由不同,此路由将使用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中,我们创建一个post请求到这个登陆路由,如下所示:
在这个请求体内,创建一个如下的json:
点击发送应该得到以下响应:
发布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
如果你复制这个access_token
到 JWT.IO Debugger中,你会看到相同的用户名和密码,只不过多了一些其他的消息,
旁注 - 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
,你应该会看到一个大的错误提示:
要解决此问题,请转到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具有签名这一事实使每个人都可以更轻松地识别信息发送者。您只需要正确的密钥即可