nodejs jwt_使用NodeJS进行JWT身份验证的实用指南

nodejs jwt

更好的编程 (BETTER PROGRAMMING)

Have you tried to integrate JWT authentication into your Node.js application, but never found the right solution? Then you have come to the right place. In this post, we are going to walk you through the finer details of JWT authentication in Node.js using the npm package, jsonwebtoken.

您是否尝试过将JWT身份验证集成到您的Node.js应用程序中,但从未找到正确的解决方案? 那么你来对地方了。 在本文中,我们将使用npm包jsonwebtoken向您介绍Node.js中JWT身份验证的详细信息。

If you are still not sure about what exactly JWT is and how it works, you can follow our previous post, before continuing with the implementation. As we discussed in the previous post, we will be following the best practices of JWT authentication in this implementation. In case you want to refresh your memory on JWTs first, let’s go through which best practices we are going to be following in this tutorial.

如果您仍然不确定JWT到底是什么以及它如何工作,则可以按照我们之前的文章进行操作,然后再继续实施。 正如我们在上一篇文章中讨论的那样,我们将在此实现中遵循JWT身份验证的最佳实践。 如果您想首先在JWT上刷新内存,让我们了解一下本教程中将遵循的最佳实践。

  • Send the JWT token in a cookie instead of the HTTP header

    在Cookie中而不是HTTP标头中发送JWT令牌
  • Set a short expiration time for the token

    为令牌设置较短的到期时间
  • Use refresh tokens to re-issue access tokens that expire in a short time

    使用刷新令牌重新发布在短时间内到期的访问令牌

There are two considerations I want to highlight before getting into the details:

在进入细节之前,我需要强调两个注意事项:

  • Writing your own implementation for authentication is not always the best solution. There are several third parties offerings that can handle all of it for you in a very secure manner.

    编写自己的身份验证实现并非总是最好的解决方案。 有几种第三方产品可以非常安全的方式为您处理所有这些。
  • The code introduced in this tutorial is an implementation for a monolith application. If you want to use this code for a microservice you will have to use a combination of public/private keys to sign and verify the token.

    本教程中介绍的代码是用于整体应用程序的实现。 如果要将此代码用于微服务,则必须使用公钥/私钥的组合来签名和验证令牌。

Now that we have got the targets set, let’s start the implementation.

现在已经设置了目标,让我们开始实施。

初步准备… (Initial preparations…)

If you haven’t already, set up the Node environment for this project. Then, install the following packages that we will be using for this tutorial.

如果尚未安装,请为此项目设置Node环境。 然后,安装我们将在本教程中使用的以下软件包。

  • Express: The Node.js framework we will be using

    表达:我们将使用的Node.js框架
  • Cookie-Parser: Since we will be sending the JWT token in a cookie, use this package to parse the cookie sent along with requests

    Cookie解析器:由于我们将在Cookie中发送JWT令牌,因此请使用此包来解析与请求一起发送的Cookie
  • Body-Parser: This package to parses the body of incoming requests to extract POST parameters

    Body-Parser:此程序包解析传入请求的正文以提取POST参数
  • Dotenv: This package loads environment variables from .env file to the application environment

    Dotenv:此软件包将环境变量从.env文件加载到应用程序环境
  • Json-Web-Token: This is the package that helps us with the JWT implementation

    Json-Web-Token:这是可以帮助我们实现JWT的软件包

You can install these packages using the following command.

您可以使用以下命令安装这些软件包。

npm install express cookie-parser bory-parser dotenv json-web-token --save

Now we’ll set up the backend of the application in the main file of the project, app.js.

现在,我们将在项目的主文件app.js设置应用程序的后端。

require('dotenv').config()
const express = require('express')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const app = express()const {login, refresh} = require('./authentication')
app.use(bodyParser.json())
app.use(cookieParser())app.post('/login', login)
app.post('/refrsh', refresh)

In the next steps, we will implement the functions inside controller.js, which we have used in the above code. We use the login function to handle post requests sent to the /login route and log in users. We use refresh function to handle post requests sent to the /refresh route and issue new access tokens using the refresh token.

在接下来的步骤中,我们将在上面的代码中使用的controller.js内部实现函数。 我们使用登录功能来处理发送到/ login路由的发布请求并登录用户。 我们使用刷新功能来处理发送到/ refresh路由的发布请求,并使用刷新令牌发布新的访问令牌。

设置环境变量 (Set up environment variables)

Before implementing the logic to log a user in, we need to set up the environment variables that are needed to configure JWTs. Create a .env file and add these two variables that we will be using inside the application.

在实现用于登录用户的逻辑之前,我们需要设置配置JWT所需的环境变量。 创建一个.env文件,并添加我们将在应用程序内部使用的这两个变量。

ACCESS_TOKEN_SECRET=swsh23hjddnns
ACCESS_TOKEN_LIFE=120
REFRESH_TOKEN_SECRET=dhw782wujnd99ahmmakhanjkajikhiwn2n
REFRESH_TOKEN_LIFE=86400

You can add any string as the secret. It’s recommended to use a longer secret with random characters as a security measure. The expiration time of an access token we create will be 120 seconds. We also set the secret to signing the refresh token and its expiration time. Note how the refresh token has a longer lifetime compared to the access token.

您可以添加任何字符串作为密码。 建议使用带有随机字符的较长密码作为安全措施。 我们创建的访问令牌的到期时间将为120秒。 我们还将密码设置为签署刷新令牌及其有效时间。 请注意,与访问令牌相比,刷新令牌的寿命更长。

处理用户登录和JWT令牌创建 (Handle user login and JWT token creation)

Now we can get to the step of implementing the login function we imported to the app.js file to handle the /login route.

现在,我们可以执行实现导入到app.js文件中的登录功能以处理/login路由的步骤。

We are going to store an array of user objects in the application for the purpose of this implementation. In a real-world scenario, you will be retrieving this user information from a database or any other location. Also, this is for demo purposes only, NEVER EVER store the actual passwords.

为了实现此目的,我们将在应用程序中存储一组用户对象。 在实际情况下,您将从数据库或其他任何位置检索此用户信息。 另外,这仅是出于演示目的, 从不存储实际密码。

nodejs jwt_使用NodeJS进行JWT身份验证的实用指南_第1张图片
Don’t save passwords in plain text 不要以纯文本格式保存密码
let users = {
john: {password: "passwordjohn"},
mary: {password:"passwordmary"}
}

When implementing the login function, first we need to retrieve the username and password sent with the login POST request.

在实现登录功能时,首先我们需要检索随登录POST请求发送的用户名和密码。

const jwt = require('json-web-token')// Never do this!
let users = {
john: {password: "passwordjohn"},
mary: {password:"passwordmary"}
}exports.login = function(req, res){ let username = req.body.username
let password = req.body.password

// Neither do this!
if (!username || !password || users[username] !== password){
return res.status(401).send()
}
}

If the request doesn’t contain either the username or the password, the server responds with a 401 unauthorized status. The same response applies if the password sent with the request does not match the password stored in the database for that particular username.

如果请求中不包含用户名或密码,则服务器将以401未经授权状态进行响应。 如果随请求发送的密码与特定用户名的数据库中存储的密码不匹配,则适用相同的响应。

If the client has sent correct credentials to the server, then we proceed to log in the user to the system by issuing new JWT tokens. In this case, the new logging-in user receives two tokens: access token and refresh token. The access token is then sent along with the response inside a cookie back to the client. The refresh token is stored in the database for issuing access tokens in the future. In our case, we will store the refresh token in the user array we previously created.

如果客户端已将正确的凭证发送到服务器,那么我们将通过发行新的JWT令牌继续将用户登录到系统。 在这种情况下,新的登录用户将收到两个令牌:访问令牌和刷新令牌。 然后,访问令牌与响应一起在Cookie中发送回客户端。 刷新令牌存储在数据库中,以便将来发布访问令牌。 在本例中,我们将刷新令牌存储在我们先前创建的用户数组中。

const jwt = require('json-web-token')// Never do this!
let users = {
john: {password: "passwordjohn"},
mary: {password:"passwordmary"}
}exports.login = function(req, res){ let username = req.body.username
let password = req.body.password

// Neither do this!
if (!username || !password || users[username].password !== password){
return res.status(401).send()
} //use the payload to store information about the user such as username, user role, etc.
let payload = {username: username} //create the access token with the shorter lifespan
let accessToken = jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET, {
algorithm: "HS256",
expiresIn: process.env.ACCESS_TOKEN_LIFE
}) //create the refresh token with the longer lifespan
let refreshToken = jwt.sign(payload, process.env.REFRESH_TOKEN_LIFE, {
algorithm: "HS256",
expiresIn: process.env.REFRESH_TOKEN_LIFE
}) //store the refresh token in the user array
users[username].refreshToken = refreshToken //send the access token to the client inside a cookie
res.cookie("jwt", accessToken, {secure: true, httpOnly: true})
res.send()
}

When sending the access token inside a cookie, remember to set the httpOnly flag to prevent attackers from accessing the cookie from the client-side. We have also set the secure flag in the above example. However, if you are only trying out this code over an HTTP connection, not an HTTPS connection, remove the secure flag to send it with the response.

在cookie中发送访问令牌时,请记住设置httpOnly标志,以防止攻击者从客户端访问cookie。 在上面的示例中,我们还设置了安全标志。 但是,如果仅通过HTTP连接而不是HTTPS连接尝试此代码,请删除安全标志以将其与响应一起发送。

添加中间件以验证用户请求 (Add a middleware to authenticate user requests)

The server needs to check whether a user has logged in before giving access to certain routes. We can use the access token sent in a cookie with every request to verify that the user is, in fact, authenticated. This process is carried out in a middleware.

在访问某些路由之前,服务器需要检查用户是否已登录。 我们可以将Cookie中发送的访问令牌与每个请求一起使用,以验证用户是否已通过身份验证。 此过程在中间件中执行。

Let’s create a new file named middleware.js and implement the verify method to check whether the user is authenticated.

让我们创建一个名为middleware.js的新文件,并实现verify方法来检查用户是否已通过身份验证。

First, we should retrieve the access token from the cookie sent with the request. If the request does not contain the access token, it will not proceed to the intended route and instead, return a 403 forbidden error.

首先,我们应该从与请求一起发送的cookie中检索访问令牌。 如果请求不包含访问令牌,它将不会继续执行预期的路由,而是返回403禁止错误。

const jwt = require('json-web-token')exports.verify = function(req, res, next){
let accessToken = req.cookies.jwt //if there is no token stored in cookies, the request is unauthorized
if (!accessToken){
return res.status(403).send()
}
}

If the request contains the access token, then the server will verify whether it was issued by the server itself using the stored secret. In case the token is expired or recognized as a one not signed by the server, the jsonwebtoken’s verify method will throw an error. We can handle the error to return a 401 error back to the client.

如果请求包含访问令牌,则服务器将使用存储的密码验证它是否由服务器本身发出。 如果令牌已过期或被识别为服务器未签名的令牌,则jsonwebtoken的verify方法将引发错误。 我们可以处理该错误,以将401错误返回给客户端。

const jwt = require('json-web-token')exports.verify = function(req, res, next){
let accessToken = req.cookies.jwt //if there is no token stored in cookies, the request is unauthorized
if (!accessToken){
return res.status(403).send()
} let payload
try{
//use the jwt.verify method to verify the access token
//throws an error if the token has expired or has a invalid signature
payload = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET)
next()
}
catch(e){
//if an error occured return request unauthorized error
return res.status(401).send()
}
}

Now we can use this middleware to protect any route that requires the users to be logged in before accessing. Import the middleware to the place you are handling routes, which, in our case, is app.js. If we are trying to protect a route named /comments, it can be easily achieved by adding the middleware before the route handler.

现在,我们可以使用此中间件来保护需要用户登录才能访问的任何路由。 将中间件导入到您要处理路线的地方,在我们的例子中是app.js 如果我们试图保护名为/ comments的路由,则可以通过在路由处理程序之前添加中间件来轻松实现。

const {verify} = require('./middleware')app.get('/comments', verify, routeHandler)

The request will be passed on to the route handler only if the user is authenticated.

仅当用户通过身份验证后,该请求才会传递到路由处理程序。

使用刷新令牌发行新的访问令牌 (Issuing new access token using the refresh token)

Remember the /refresh route and refresh function we had used in the initial code in the app.js file? Now we can implement this refresh function to issue new access tokens using the stored refresh token.

还记得我们在app.js文件的初始代码中使用的/ refresh路由和刷新功能吗? 现在,我们可以实现此刷新功能,以使用存储的刷新令牌发行新的访问令牌。

The function used here, refresh, is also inside the controller.js file we used before for the login function implementation.

此处使用的功能(刷新)也位于我们之前用于登录功能实现的controller.js文件中。

The first part of this function is much like what we did to verify an access token: Check whether the access token was sent and verify the token.

该功能的第一部分与我们验证访问令牌的操作非常相似:检查是否已发送访问令牌并验证令牌。

exports.refresh = function (req, res){    let accessToken = req.cookies.jwt    if (!accessToken){
return res.status(403).send()
} let payload
try{
payload = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET)
}
catch(e){
return res.status(401).send()
}
}

Then, we are going to retrieve the refresh token of this particular user using the username stored in access token’s payload. Once the refresh token is verified, the server will issue a new access token as implemented in the login function.

然后,我们将使用存储在访问令牌有效负载中的用户名来检索此特定用户的刷新令牌。 验证刷新令牌后,服务器将发出登录功能中实现的新访问令牌。

If the refresh token is expired or fails to verify, the server will return an unauthorized error. Otherwise, the new access token will be sent in the cookie.

如果刷新令牌已过期或无法验证,则服务器将返回未授权的错误。 否则,新的访问令牌将在cookie中发送。

exports.refresh = function (req, res){    let accessToken = req.cookies.jwt    if (!accessToken){
return res.status(403).send()
} let payload
try{
payload = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET)
}
catch(e){
return res.status(401).send()
} //retrieve the refresh token from the users array
let refreshToken = users[payload.username].refreshToken //verify the refresh token
try{
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET)
}
catch(e){
return res.status(401).send()
} let newToken = jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET,
{
algorithm: "HS256",
expiresIn: process.env.ACCESS_TOKEN_LIFE
}) res.cookie("jwt", newToken, {secure: true, httpOnly: true})
res.send()
}

This step completes our implementation of JWT authentication with Node.js

这一步完成了我们对Node.js的JWT身份验证的实现

摘要 (Summary)

In this tutorial, we went through the steps of implementing authentication with JWT in Node.js. As a continuation of our previous post, where we discussed the theories behind JWT authentication, our implementation was focused on adhering to the best practices we discussed before. As a result, our JWT implementation made use of cookies to send JWTs and refresh tokens to generate new access tokens. If you are willing to take a step ahead of this implementation, you can come up with a solution to re-issue refresh tokens within a short timespan to avoid security breaches.

在本教程中,我们完成了在Node.js中使用JWT实现身份验证的步骤。 作为我们先前文章的继续,我们讨论了JWT身份验证背后的理论,我们的实现重点是坚持我们之前讨论的最佳实践。 结果,我们的JWT实现使用cookie来发送JWT,并刷新令牌以生成新的访问令牌。 如果您愿意在此实现之前迈出一步,则可以提出一种解决方案,以在较短的时间内重新发出刷新令牌,从而避免安全漏洞。

Thanks for reading!

谢谢阅读!

翻译自: https://levelup.gitconnected.com/a-practical-guide-to-jwt-authentication-with-nodejs-e0e425376733

nodejs jwt

你可能感兴趣的:(nodejs)