passport身份验证
This is a step by step guide to help you create email/password authentication with passport.js.
这是一个逐步指南,可帮助您使用passport.js创建电子邮件/密码验证。
先决条件 (Prerequisites)
In order to follow along this post, you need to have a basic understanding of Node.js, Express.js and MongoDB.
为了继续阅读本文,您需要对Node.js,Express.js和MongoDB有基本的了解。
通行证简介 (Introduction to passport.js)
According to Passport’s website, passport is defined as follows:
根据Passport的网站,护照的定义如下:
“Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped into any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.”
“ Passport是 Node.js的 身份验证中间件 。 Passport非常灵活和模块化,可以毫不费力地放入任何 基于 Express 的Web应用程序中。 一套全面策略支持认证使用 的用户名和密码 , Facebook的 , Twitter的 ,和 更多 “。
Yes, passport is a middleware. It is flexible and modular in a sense that we can install different strategies that help us achieve different tasks. Strategies are the tools that help us perform user authentication using username and password or other OAuth options. If you don’t know what strategies do, don’t worry, you will be able to understand what it does after we implement strategies in a while.
是的,护照是一种中间件。 从某种意义上说,它是灵活的和模块化的,我们可以安装不同的策略来帮助我们完成不同的任务。 策略是可以帮助我们使用用户名和密码或其他OAuth选项执行用户身份验证的工具。 如果您不知道策略的作用,请放心,在我们实施策略一段时间后,您将能够了解它的作用。
Now that we have understood what passport.js does, let’s start implementing it. For that, let’s open up the terminal in our main working directory and write the following command:
现在我们已经了解了passport.js的作用,让我们开始执行它。 为此,让我们在主工作目录中打开终端并编写以下命令:
npm init -y
The -y
flag here sets every value to default.
这里的-y
标志将每个值设置为默认值。
Let’s now install some of the packages we need.
现在让我们安装一些我们需要的软件包。
npm i bcrypt connect-mongo express express-session express-validator mongoose passport passport-local
Let’s now create a file called index.js inside of our main project directory and write the following lines of code:
现在,在主项目目录中创建一个名为index.js的文件,并编写以下代码行:
First few lines are imports. From line 1 to 4, we have imported express
, mongoose
, express-session
, and connect-mongo
. Here, the task of express-session
will be to create sessions; the task of connect-mongo
is to help us store the sessions in our mongoDB database. Notice that, at line 4 connect-mongo
takes in session
as the argument.
前几行是导入。 从第1行到第4行,我们导入了express
, mongoose
, express-session
和connect-mongo
。 在这里, express-session
的任务是创建会话。 connect-mongo
的任务是帮助我们将会话存储在mongoDB数据库中。 请注意,在4号线connect-mongo
发生在session
作为参数。
Let’s now take a look at line 6, we have imported MONGO_URI
and SECRET
from ./config. MONGO_URI
is the MongoDB database URI while SECRET
is a secret value which we require while parsing cookies. We will be working on ./config shortly.
现在让我们看一下第6行,我们已经从./config导入了MONGO_URI
和SECRET
。 MONGO_URI
是MongoDB数据库URI,而SECRET
是我们在解析Cookie时需要的秘密值。 我们很快将在./config上工作。
We then imported passport
and AuthRoute
at line 8 and 9 respectively. We’ll work on passport
and AuthRoute
in a while.
然后,我们分别在第8行和第9行导入了passport
和AuthRoute
。 我们将在一段时间内处理passport
和AuthRoute
。
At line 11, we initialized our express app.
在第11行,我们初始化了Express应用程序。
Then, at line 13, we used express.json()
middleware with the help of app.use(express.json())
.
然后,在第13行,借助于app.use(express.json())
使用了express.json()
中间件。
Then, from line 14 to 24, we have written code which uses session
middleware in our app
. The session takes in key value pairs which takes multiple options. Let’s discuss those options. At line 16, we have a secret
option. It is self-explanatory, right? At line 17, we have resave
option which takes in Boolean value. We don’t want to resave the session. So, we have set it to false
. At line 18, we have saveUninitialized
option which also takes in Boolean. We don’t want to save uninitialized sessions. So, we have set it to false
. Then, at line 19, we have the cookie
option, which takes in the secure
option. The secure
option takes in Boolean value. In our case we have set secure
to false
. Since, we will be working on localhost
, the secure
option is set to false
and if we set the secure
option to true
we need to have https:// . Remember that we need to set secure
to true
when we publish our app in production. Now, let’s take a look at line 20. There, we have an option called store
. The store
takes in the information about the database or just the memory address where the session should be stored. In our case, we are storing our session in MongoDB. For that, we have used MongoStore
. We created a new instance of MongoStore
with an option called mongooseConnection
. The mongooseConnection
takes in the mongoose.connection
.
然后,从第14行到第24行,我们编写了在app
使用session
中间件的代码。 会话采用键值对,键对具有多个选项。 让我们讨论这些选项。 在第16行,我们有一个secret
选项。 这是不言自明的,对吧? 在第17行,我们有resave
选项,它接受布尔值。 我们不想重新保存会话。 因此,我们将其设置为false
。 在第18行,我们有saveUninitialized
选项,该选项也包含布尔值。 我们不想保存未初始化的会话。 因此,我们将其设置为false
。 然后,在第19行,我们有cookie
选项,该选项接受secure
选项。 secure
选项采用布尔值。 在我们的情况下,我们将secure
设置为false
。 既然,我们将上工作的localhost
的secure
选项设置为false
,如果我们设置secure
选项, true
我们需要有https://开头 。 请记住,在生产环境中发布应用程序时,需要将secure
设置为true
。 现在,让我们看一下第20行。在这里,我们有一个名为store
的选项。 store
接收有关数据库的信息,或者仅接收应存储会话的内存地址。 就我们而言,我们将会话存储在MongoDB中。 为此,我们使用了MongoStore
。 我们创建的新实例MongoStore
与所谓的选项mongooseConnection
。 mongooseConnection
接受mongoose.connection
。
At line 26, we have initialized the passport
, which we had imported at line 8, using passport.initialize()
. Then, at line 27, we have written code to tell the passport that it has to use session for the authentication process.
在第26行,我们初始化了passport
,这是我们在第8行已经进口,使用passport.initialize()
然后,在第27行,我们编写了代码来告诉护照它必须使用会话进行身份验证过程。
At line 29, we have created an endpoint /api/auth with the help of AuthRoute
.
在第29行,我们借助AuthRoute
创建了一个端点/ api / auth 。
At line 31, we have written code to store the value of the port, in which our server will run. We check if the port exists in the environment variable. If it exists, we store the value from the environment variable, else we use the default value as 5000.
在第31行,我们编写了代码来存储服务器将在其中运行的端口的值。 我们检查端口是否存在于环境变量中。 如果存在,我们将存储环境变量中的值,否则将使用默认值5000。
From line 33 to 36, we have written code to connect to the MongoDB database. It’s simple right?
从第33行到第36行,我们编写了连接MongoDB数据库的代码。 很简单吧?
Then, at line 38, we have written code to start our server at the given PORT
.
然后,在第38行,我们编写了代码以在给定PORT
处启动服务器。
Now that we have understood what’s happening inside of index.js file, let’s now work on ./config. Let’s create a directory called config in our main project directory. After that, let’s create three files called index.js, prod.js, and dev.js inside of the config directory.
既然我们已经了解了index.js文件内部发生了什么,那么现在让我们在./config上工作。 让我们在主项目目录中创建一个名为config的目录。 之后,让我们在config目录中创建三个名为index.js , prod.js和dev.js的文件。
Let’s now open up the file index.js of the config directory and write the following lines of code:
现在让我们打开config目录的文件index.js并编写以下代码行:
if (process.env.NODE_ENV === "production") { module.exports = require("./prod");} else { module.exports = require("./dev");}
The above code either exports the modules from prod.js or dev.js file. If the process.env.NODE_ENV
is equal to "production"
, we will export modules from prod.js else we will export modules from dev.js.
上面的代码从prod.js或dev.js文件中导出模块。 如果process.env.NODE_ENV
等于"production"
,那么我们将从prod.js导出模块,否则我们将从dev.js导出模块。
Let’s now open up dev.js file and write the following lines of code:
现在让我们打开dev.js文件并编写以下代码行:
module.exports = {MONGO_URI: ,// add mongoDB URI hereSECRET: ,// add Secret here};
Inside of this file, we are exporting MONGO_URI
and SECRET
. We need to enter their respective values as mentioned in the comment above. The MONGO_URI
takes in the mongoDB database URL while the SECRET
takes in some secret value. We can enter any random string in the SECRET
. Remember, this file gets exported only when the NODE_ENV
is not "production"
.
在此文件内部,我们正在导出MONGO_URI
和SECRET
。 我们需要输入上面评论中提到的它们各自的值。 MONGO_URI
接受mongoDB数据库URL,而SECRET
接受一些秘密值。 我们可以在SECRET
输入任何随机字符串。 请记住,仅当NODE_ENV
不是"production"
时, NODE_ENV
导出该文件。
Now that we have worked on dev.js file, let’s now work on prod.js file. Let’s write the following lines of code inside of the prod.js file:
现在我们已经处理了dev.js文件,现在让我们处理prod.js文件。 让我们在prod.js文件中编写以下代码行:
module.exports = {MONGO_URI: process.env.MONGO_URI,SECRET: process.env.SECRET,};
Inside of prod.js file, we have written code to export MONGO_URI
and SECRET
. The MONGO_URI
takes in process.env.MONGO_URI
as the value from our environment variables. Similarly, SECRET
takes in process.env.SECRET
as the value. Remember, these modules get exported only when NODE_ENV
is "production"
. So, in this article, we won’t be spending time on creating environment variables, okay?
在prod.js文件中,我们编写了导出MONGO_URI
和SECRET
代码。 MONGO_URI
将process.env.MONGO_URI
作为我们环境变量的值。 同样, SECRET
将process.env.SECRET
用作值。 请记住,仅当NODE_ENV
为"production"
时, NODE_ENV
导出这些模块。 因此,在本文中,我们不会花时间在创建环境变量上,好吗?
Let’s now work on our routes. For that, let’s create a directory called routes inside of our main project directory. Then, create a file called Auth.js inside of the routes directory. Let’s now write the following lines of code inside of the file Auth.js:
现在开始我们的路线。 为此,让我们在主项目目录中创建一个名为route的目录。 然后,在路由目录中创建一个名为Auth.js的文件。 现在让我们在文件Auth.js内编写以下代码行:
First few lines are just imports. At line 1, we imported express
. At line 2, we imported the passport
. At line 3, we imported check
and validationResult
from express-validator
. At line 5, we imported our controllers namely login
, logout
, me
and signup
.
前几行只是导入。 在第1行,我们导入了express
。 在第二行,我们进口了passport
。 在第3行,我们进口的check
,并validationResult
从express-validator
。 在第5行,我们导入了控制器,即login
, logout
, me
和signup
。
At line 7, we created the router
with the help of express.Router()
.
在第7行,我们借助express.Router()
创建了router
。
From line 10 to 50, we have written code for our api/auth/signup
route. From line 12 to 37, we have written code for validating data using express-validator
. If you don’t know the basics of express-validator
, you might want to take a look at this article which I have written.
从第10行到第50行,我们已经为api/auth/signup
编写了代码 路线。 从第12行到第37行,我们编写了使用express-validator
验证数据的代码。 如果您不了解express-validator
的基础知识,则可能需要看一下我写的这篇文章。
Let’s go through line 12 to 37 a bit fast. From line 13 to 16, we have written code to specify that the name
field should have minimum length of 3. If the minimum length isn’t 3, we generate a validation error message with .withMessage
. From line 18 to 21, we wrote code to check if the email
field has an actual email address. Similarly, from line 23 to 29, we wrote code to validate the password field.
让我们快速通过第12至37行。 从第13行到第16行,我们编写了代码以指定name
字段的最小长度应为3。如果最小长度不是3,则将使用.withMessage
生成验证错误消息。 从第18行到第21行,我们编写了代码来检查email
字段中是否有实际的电子邮件地址。 同样,从第23行到第29行,我们编写了代码来验证密码字段。
We specified that the password should have minimum length of 8 and max length of 15. We also specified that the password field should have at least one number and at least one special character. From line 31 to 36, we have written code to check if the confirmPassword
and password
field have the same value. At line 39, we extracted and formatted the errors. Then we checked if the error exists or not. If the error exists, we send the user with an array of error messages with status code 422 at line 44. If errors don’t exist, we run next()
at line 46.
我们指定密码的最小长度为8,最大长度为15。我们还指定密码字段应至少具有一个数字和至少一个特殊字符。 从第31行到第36行,我们编写了代码来检查confirmPassword
和password
字段是否具有相同的值。 在第39行,我们提取并格式化了错误。 然后,我们检查错误是否存在。 如果存在错误,则在第44行向用户发送一系列错误消息,状态代码为422。如果不存在错误,则在第46行运行next()
。
Then at line 49, we specified that the controller for the /api/auth/signup
route should be signup
.
然后在第49行,我们指定/api/auth/signup
的控制器 路线应该signup
。
From line 53 to 59, we created a route for /api/auth/login
endpoint. We have used passport.authenticate
as the middleware for this endpoint. The authenticate
takes in two arguments. The first argument takes in strategy name. In our case it is local
. The second argument takes in an object. We can see that we have used an option called failureMessage
in our second argument. The purpose of this option is self explanatory, right? At line 58, we have specified that the controller for /api/auth/login
is login
.
从第53行到第59行,我们为/ api/auth/login
创建了一条路由 终点。 我们已使用passport.authenticate
作为此端点的中间件。 authenticate
接受两个参数。 第一个参数采用策略名称。 在我们的情况下,它是local
。 第二个参数接受一个对象。 我们可以看到我们在第二个参数中使用了一个名为failureMessage
的选项。 这个选项的目的是不言自明的,对吧? 在第58行,我们指定/ api/auth/login
的控制器为login
。
Then, at line 62, we have created an endpoint for /api/auth/logout
. We have also specified that this route should have the logout
as the controller.
然后,在第62行,我们为/api/auth/logout
创建了一个端点。 我们还指定了该路由应以logout
作为控制器。
Then at line 65, we have created an endpoint for /api/auth/me
route. We have also specified, this route should have me
as the controller. The purpose of the me
controller will be to get the logged in user’s info.
然后在第65行,我们为/api/auth/me
路由创建了一个端点。 我们还指定了,此路由应以me
为控制者。 me
控制器的目的是获取登录用户的信息。
Let’s now work on models. For that, let’s create a directory called models inside of the main project directory. Let’s now create a file called User.js in the models directory. Let’s now write the following lines of code inside of the file:
现在让我们来研究模型。 为此,让我们在主项目目录中创建一个名为models的目录。 现在让我们在models目录中创建一个名为User.js的文件。 现在让我们在文件内部编写以下代码行:
At line 1, we imported mongoose
. Then, at line 2, we imported bcrypt
. We then created a UserSchema
from line 12 to 25. The user schema has name
, email
, password
fields. All of these fields are String
.
在第1行,我们进口了mongoose
。 然后,在第2行,我们导入了bcrypt
。 然后,我们从第12行到第25行创建了一个UserSchema
。该用户架构具有name
, email
和password
字段。 所有这些字段都是String
。
Then, at line 12, we used the pre("save",...)
method. At line 13, we stored this
in the user
constant. After that, at line 16, we checked if the password
field of the user
is modified or not. If the password
field isn’t modified, we use next()
else, we go on to hash the password
. We have written code to hash the password and store it in the variable hash
at line 18. Here, we used bcrypt.hash
to hash the password. The bcrypt.hash
takes in plain text password as the first argument and hash round as the second argument. We have specified 13 as the hash round. We then saved the hash in the user.password
at line 19. After that, we used next()
at line 20. If there occurs any error while hashing the password, we have written code to log the error at line 22. Then, we use next(error)
at line 23.
然后,在第12行,我们使用了pre("save",...)
方法。 在第13行,我们保存this
在user
不断。 之后,在第16行,我们检查了user
的password
字段是否被修改。 如果password
字段未修改,则使用next()
else,然后继续对password
进行哈希处理。 我们已经编写了用于对密码进行哈希处理的代码,并将其存储在第18行的变量hash
中。在这里,我们使用bcrypt.hash
来对密码进行哈希处理。 bcrypt.hash
将纯文本密码作为第一个参数,将哈希回合作为第二个参数。 我们已将13指定为哈希回合。 然后,将散列保存在第19行的user.password
中。之后,在第20行使用next()
。如果在对密码进行散列时发生任何错误,我们已经编写了代码以在第22行记录该错误。然后,我们在第23行使用next(error)
。
Now, at line 27 we created the comparePassword
method using UserSchema.methods.comparePassword
. This method will help us to compare hashed password with plain text password. At line 29, we have written code to compare the plain text password (password
) with the hashed password (this.password
) and stored it in the result
variable. The result
will be true if the passwords match. We then return the result
at line 31. If there occurs any error while carrying out this method, we log the error at line 33. Then, we return false
, at line 34. We will be using this method while working on passport middleware.
现在,在第27行,我们使用UserSchema.methods.comparePassword
创建了comparePassword
方法。 此方法将帮助我们比较哈希密码和纯文本密码。 在第29行,我们编写了代码,将纯文本密码( password
)与哈希密码( this.password
)进行比较,并将其存储在result
变量中。 如果密码匹配,则result
为true。 然后,我们在第31行返回result
。如果在执行此方法时发生任何错误,则在第33行记录错误。然后,在第34行返回false
。在护照中间件上工作时将使用此方法。
At line 38, we exported the user model.
在第38行,我们导出了用户模型。
Let’s now work on our controllers. For that, let’s create a directory called controller inside of our main project directory. After that, let’s create a file called AuthController.js inside of the controller directory.
现在让我们在控制器上工作。 为此,让我们在主项目目录中创建一个名为controller的目录。 之后,让我们在控制器目录内创建一个名为AuthController.js的文件。
At line 1, we have imported the User
model.
在第1行,我们导入了User
模型。
At line 3, you can see that we have a function called login
. At line 4, we have written code to send the user with the message that the user has successfully logged in and the status code of 200. Remember that, the logging in process is carried out by the passort.authenticate
middleware, and this controller only gets called when the authentication is successful.
在第3行,您可以看到我们有一个名为login
的函数。 在第4行,我们编写了代码,向用户发送用户已成功登录的消息以及状态码200。请记住,登录过程由passort.authenticate
中间件执行,并且仅此控制器认证成功时被调用。
Let’s take a look at the signup
function from line 7 to 24. At line 8, we stored the values of email
, name
, password
, from req.body
. At line 10, we wrote the code to query our User
model with the email
. If there already exists the user with the same email
, we send the user with an error message that the user already exists (from line 16 to 18). If the user with the provided email
does not exist, we have written code to create a new user at line 12. After that, we saved the user data using user.save()
at line 13. Then, we wrote code to send the user the success message at line 14. If there occurs any error during the process, we would send the user with the status code of 500 and error message ( at line 22 ).
让我们看一下第7到24行的signup
功能。在第8行,我们存储了来自req.body
的email
, name
, password
的值。 在第10行,我们编写了代码,通过email
查询User
模型。 如果已经存在使用相同email
发送给用户的用户,我们将向用户发送一条错误消息,提示该用户已经存在(从第16行到第18行)。 如果提供的email
用户不存在,我们将在第12行编写代码以创建新用户。此后,我们在第13行使用user.save()
保存用户数据。然后,编写代码以发送在第14行向用户发送成功消息。如果在此过程中发生任何错误,我们将向用户发送状态代码500和错误消息(在第22行)。
Let’s now take a look at line 26. There, we have a function called logout
. Remember that it gets called when a user makes a request to the route /api/auth/logout
. At line 27, we used req.logout()
to log the user out. After logging the user out, we have written code to send the user the success message at line 28.
现在让我们看一下第26行。在这里,我们有一个名为logout
的函数。 请记住,当用户向路由/api/auth/logout
请求时,它会被调用。 在第27行,我们使用req.logout()
将用户注销。 在注销用户之后,我们编写了代码以在第28行向用户发送成功消息。
Let’s now take a look at theme
function at line 31. Here, we checked if the user is logged in with the help of req.user
. If the req.user
doesn’t exist, we have written code to send the user with the error message and a status of 403 at line 32 and 33. If the req.user
exists, we send the user info (at line 35).
现在让我们看一下第31行的me
函数。在这里,我们检查了用户是否在req.user
的帮助下req.user
。 如果req.user
不存在,我们已经编写了代码以在32和33行向用户发送错误消息以及状态为403。如果req.user
存在,我们将发送用户信息(在35行) 。
From line 38 to 43, we have exported login
, signup
, logout
and me
.
从第38行到第43行,我们导出了login
, signup
, logout
和me
。
Let’s now work on passport middleware. For that, let’s create a directory called services inside of the main project directory. Then, create a file called passport.js inside of the services directory and write the following lines of code:
现在让我们研究护照中间件。 为此,让我们在主项目目录中创建一个名为services的目录。 然后,在services目录内创建一个名为passport.js的文件,并编写以下代码行:
Here, The first few lines are just imports. At line 1, we imported the passport
. At line 2, we imported the LocalStrategy
. At line 5, we imported the User
model.
在这里,前几行只是导入。 在第一行,我们进口了passport
。 在第2行,我们导入了LocalStrategy
。 在第5行,我们导入了User
模型。
At line 7, we have used passport.serializeUser
. The function of serializeUser
is to tell the passport which data of the user should be stored in the session. That specified data will later be used by deserializeUser
to get user’s information. At line 8, we specified what value should be stored in session by using done(null, user._id)
. With this command we are telling the passport that the session should store user’s id.
在第7行,我们使用了passport.serializeUser
。 serializeUser
的功能是告诉护照应将用户的哪些数据存储在会话中。 该指定的数据稍后将由deserializeUser
以获取用户的信息。 在第8行,我们通过使用done(null, user._id)
指定了应在会话中存储的值。 使用此命令,我们告诉护照会话应该存储用户的ID。
Let’s now take a look at deserializeUser
at line 11. The function of deserializeUser
is to receive the information about the user from the given user’s information stored in the session. Let’s take a closer look. Here, deserializUser
provides us with id
and done
. At line 13, we have queried the User model by id
and stored it in the user
variable. if the user
doesn’t exist, we throw new errors at line 15. If the user exists, we run done(null,user)
at line 17.
现在让我们来看看deserializeUser
在第11行的功能deserializeUser
是接收来自存储在会话中的给定用户的信息的用户的信息。 让我们仔细看看。 在这里, deserializUser
为我们提供了id
和done
。 在第13行,我们通过id
查询了User模型并将其存储在user
变量中。 如果该user
不存在,则在第15行抛出新错误。如果该用户存在,则在第17行运行done(null,user)
。
From line 22 to 41, we have set up the local strategy. Let’s take a closer look. With the code, we have told passport
to use local strategy by creating new a new instance of LocalStrategy
. At line 26, we have a field called usernameField
, which takes in "email"
as the value. This is used to define the username field should be "email"
. By default, its value is "username"
.
从第22行到第41行,我们制定了本地策略。 让我们仔细看看。 有了代码,我们告诉passport
通过创建新的LocalStrategy
实例来使用本地策略。 在第26行,我们有一个名为usernameField
的字段,该字段接受"email"
作为值。 这用于定义用户名字段应为"email"
。 默认情况下,其值为"username"
。
Let’s take a look at the function at line 27. It has email
, password
, done
as the arguments. At line 29, we made a query of User
model by email
and stored it in the user
variable. If the user does not exist, we run done(null, false)
at line 31. After that, at line 33, we compared the password
with the help of comparePassword
method, which we had created in our User
model and stored it in passwordMatch
constant. If the passwordMatch
is false
, we run done(null, false)
, at line 35. If the password matches, we run done(null,user)
at line 37. By using done(null,user)
we are telling passport to create a session for this particular user. If there occurs any error during the process we run done(err)
, at line 39. We then exported the passport at line 45.
让我们来看看在27行它具有功能email
, password
, done
作为参数。 在第29行,我们通过email
查询了User
模型,并将其存储在user
变量中。 如果用户不存在,则在第31行运行done(null, false)
。之后,在第33行,我们使用在User
模型中创建的comparePassword
方法比较了password
,并将其存储在passwordMatch
不变。 如果passwordMatch
为false
,则在第35行运行done(null, false)
。如果密码匹配,则在第37行运行done(null,user)
。通过使用done(null,user)
告诉护照创建此特定用户的会话。 如果在此过程中发生任何错误,请在第39行运行done(err)
。然后,在第45行导出护照。
If you are still stuck about serializeUser
or deserializeUser
, you can see this answer on stack overflow which might give you much clear picture.
如果您仍然对serializeUser
或deserializeUser
感到deserializeUser
,则可以在堆栈溢出时看到此答案,这可能会给您带来很多清晰的画面。
This means we now can authenticate our users.
这意味着我们现在可以验证我们的用户。
passport身份验证