用Express中的Prisma制作认证方案

610 阅读9分钟

在开始本教程之前,我们首先要了解什么是Prisma,以及为什么它是刚接触Express的开发者的一个好选择。我们还将讨论PostgreSQL,以及如何将其用于数据库模式和结构化。

我们还将了解Prisma的有效性,以及如何使用它进行基本认证,同时还有代码样本和测试实例来帮助你跟上这个教程。

什么是Prisma?

Prisma是一个开源的ORM,它允许你轻松地管理你的数据库并与之互动。这是通过Prisma模式完成的,在这个地方你可以使用Prisma模式语言定义你的数据库模型和关系。

你可以从头开始运行你的模式,或者通过内省现有的数据库来生成它。然后,你可以使用Prisma Client与你的数据库进行交互,并使用Prisma Migrate将你的模式迁移到数据库中。

Prisma支持PostgreSQL、MySQL、SQLite和Microsoft SQL Server。Prisma与每个Node.js后端框架互动,使数据库管理和迁移变得简单。

在Express中用Prisma建立一个认证方案

首先,我们将设置一个Express应用程序并添加Prisma。然后,我们将使用第三方包,如JWT,用于基于令牌的认证,以创建一个认证方案。最后,我们将介绍如何运行测试并确保我们的认证方案正确运行。

前提条件

要学习本教程,你应该对这些技术有一定的了解,并在你的电脑上安装它们的最新版本。

设置Express

为了安装Express,我们必须首先使用npm初始化我们的应用程序。要做到这一点,请在你的终端运行以下代码。

mkdir express-prisma
cd express-prisma

npm init -y

然后,我们可以使用npm在我们新创建的应用程序中使用以下代码安装Express。

npm install express

接下来我们使用Docker设置我们的PostgreSQL。

要做到这一点,我们将使用以下composer命令创建一个新的Docker文件。

nano docker-compose.yml

然后在我们的docker-compose.yml 文件中,我们可以添加以下代码来连接到数据库。

version: '3.8'
services:
  postgres:
    image: postgres:10.3
    restart: always
    environment:
      - POSTGRES_USER=sammy
      - POSTGRES_PASSWORD=your_password
    volumes:
      - postgres:/var/lib/postgresql/data
    ports:
      - '5432:5432'
volumes:
  postgres:

注意,POSTGRES_USERPOST_PASSWORD 是预设的用户名和密码,将用于访问数据库。

安装和配置Prisma

当我们的Express应用程序的安装和配置完成后,我们现在可以继续使用npm将Prisma安装到我们的应用程序中。要做到这一点,只需使用下面的命令。

npx prisma init

这将创建一个新的Prisma文件夹,该文件夹将包含schema.prisma ,如果不存在,也将创建一个.env 文件。

文件生成后,打开.env 文件,并添加一个链接到你的数据库。

DATABASE_URL="postgresql://<NAME_OF_DATABASE>:<DATABASE_PASSWORD>@localhost:5432/express-prisma?schema=public"

请确保使用你自定义的数据库名称和密码。

现在我们已经完成了Prisma的配置,我们可以创建一个Prisma模式并添加我们的认证方案。

创建一个Prisma模式

我们将首先创建一个模式,它将包含将被迁移到数据库的用户参数。这些将使我们能够与他们互动,以完成认证。

要添加一个模式,请进入prisma/schema.prisma 文件并添加以下代码。

model User {
  id       Int     @id @default(autoincrement())
  email    String  @unique
  name     String?
  password String?
}

一旦完成,我们就可以运行我们的迁移,它将在数据库中创建表user ,并为其添加列。

要运行迁移,在你的终端添加下面的代码并运行它。

npx prisma migrate dev --name "init" --preview-feature

如果迁移成功,将在之前创建的prisma 文件夹中创建一个新的文件夹。这个文件夹将被称为migrations ,并将包含一个SQL文件。

我的文件是20210613163752_init/migration.sql ,文件migration.sql ,其中包含在模式中创建的SQL结构。

-- CreateTable
CREATE TABLE "User" (
    "id" SERIAL NOT NULL,
    "email" TEXT NOT NULL,
    "name" TEXT,
    "password" TEXT,
    PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");

使用Prisma客户端

Prisma客户端是一个自动生成的、类型安全的查询生成器,你可以用它来从Node.js或TypeScript应用程序中以编程方式读写数据库中的数据。你将在你的REST API路线中使用它来访问数据库,取代传统的ORM、普通SQL查询、自定义数据访问层或任何其他与数据库对话的方法。

要在你的项目中安装Prisma客户端,只需在终端输入以下命令并运行它。

npm install @prisma/client

这将使你能够在你项目的任何地方使用Prisma Client,反过来,允许你与你的数据库进行交互。

索引要求

在Prisma Client设置好后,我们可以继续添加我们的控制器,它将与我们的路由(指向我们控制器中的指定功能)进行交互。我们还将添加我们的服务,这些服务与数据库或Prisma互动。

首先,我们将创建几个文件和文件夹来存放它们。首先是路由 - 我们将创建一个名为routes 的文件夹,并添加我们的文件index.jsauth.js

然后我们在根index.js 文件中启动我们的Express服务器,并将路由指向routes/index.js

接下来,我们需要在根index.js 文件中的 Prisma Client。

const express = require('express');
require('@prisma/client');
const app = express();
require('dotenv').config();
const route = require('./routes');
const bodyParser = require('body-parser');
const multer = require('multer');
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())

// redirect to routes/index.js
const route = require('./routes');
app.use('/', route);

const port = process.env.PORT || 5000;
app.listen(port, () => {
    console.log(`server is running on port ${port}`);
});

索引路由

当这一切完成后,我们就可以继续前进,将我们的路由指向routes/index.js 文件中的各个目的地。

const express = require('express');
const router = express.Router();
const auth = require('./auth');
const createError = require('http-errors')
router.get('/', (req, res) => {
    res.send('Hello World!');
});
router.use('/auth', auth);
router.use( async (req, res, next) => {
    next(createError.NotFound('Route not Found'))
})
router.use( (err, req, res, next) => {
    res.status(err.status || 500).json({
        status: false,
        message: err.message
    })
})
module.exports = router;

如果你注意到,我在我的index.js 文件中需要http-errors 包。这是因为我将使用它来拦截错误,并将其作为一个消息正确地传递给客户端。

为了利用http-errors ,你可以使用安装。

npm install http-errors

创建一个授权服务

我们将需要创建一个服务文件,在我们的数据库和控制器之间进行通信。在服务文件中,我们将创建三个函数:register,login, 和all ,它们将向数据库注册一个新的用户,获得用户的信息,并将用户登录。

all 函数将获得所有的用户,这只有在请求有一个在登录或注册时产生的有效令牌时才会发生。

首先,我们将创建一个名为services 的文件夹,然后在services 文件夹内创建一个名为auth.services.js 的文件。接下来,我们可以创建我们的register 函数,并安装bcrypt和JWT,用于密码散列和生成令牌。

要安装bcrypt和JWT,请在你的终端输入以下命令并运行它。

npm install bcryptjs jsonwebtoken

安装完成后,我们将创建一个名为utils 的文件夹,以便添加我们的JWT函数,我们稍后将使用它来生成令牌。

在我们的utils 文件夹中,创建一个名为jwt.js 的文件,并添加以下函数。

const jwt = require('jsonwebtoken')
const createError = require('http-errors')
require('dotenv').config()
const accessTokenSecret = process.env.ACCESS_TOKEN_SECRET
module.exports = {
    signAccessToken(payload){
        return new Promise((resolve, reject) => {
            jwt.sign({ payload }, accessTokenSecret, {
            }, (err, token) => {
                if (err) {
                reject(createError.InternalServerError())
                }
                resolve(token)
            })
        })
    },
    verifyAccessToken(token){
        return new Promise((resolve, reject) => {
            jwt.verify(token, accessTokenSecret, (err, payload) => {
                if (err) {
                    const message = err.name == 'JsonWebTokenError' ? 'Unauthorized' : err.message
                    return reject(createError.Unauthorized(message))
                }
                resolve(payload)
            })
        })
    }
}

然后在我们的.env 文件中,我们添加我们的ACCESS_TOKEN_SECRET

ACCESS_TOKEN_SECRET=<CUSTOM_ACCESS_TOKEN>

然后我们可以回到auth.service.js ,要求我们的JWT文件与bcrypt和Prisma一起。

// services/auth.service.js

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

require('dotenv').config();
const bcrypt = require('bcryptjs');
const jwt = require('../utils/jwt');

接下来,创建我们的register 函数,向数据库添加一个新的用户。

class AuthService {
  static async register(data) {
        const { email } = data;
        data.password = bcrypt.hashSync(data.password, 8);
        let user = prisma.user.create({
            data
        })
        data.accessToken = await jwt.signAccessToken(user);

        return data;
    }
}

module.exports = authService;

同时,我们还可以添加我们的loginall 函数。

// services/auth.service.js

static async login(data) {
        const { email, password } = data;
        const user = await prisma.user.findUnique({
            where: {
                email
            }
        });
        if (!user) {
            throw createError.NotFound('User not registered')
        }
        const checkPassword = bcrypt.compareSync(password, user.password)
        if (!checkPassword) throw createError.Unauthorized('Email address or password not valid')
        delete user.password
        const accessToken = await jwt.signAccessToken(user)
        return { ...user, accessToken }
    }
    static async all() {
        const allUsers = await prisma.user.findMany();
        return allUsers;
    }

创建一个授权控制器

为了从我们的路由中获得我们的请求主体,我们将创建一个名为controllers/auth.controller.js 的控制器,并添加我们的registerlogin ,和all 函数来与我们各自的服务进行通信。

const auth = require('../services/auth.service');
const createError = require('http-errors');
class authController {
    static register = async (req, res, next) => {
        try {
            const user = await auth.register(req.body);
            res.status(200).json({
                status: true,
                message: 'User created successfully',
                data: user
            })
        }
        catch (e) {
            next(createError(e.statusCode, e.message))
        }
    }
    static login = async (req, res, next) => {
         try {
            const data = await auth.login(req.body)
            res.status(200).json({
                status: true,
                message: "Account login successful",
                data
            })
        } catch (e) {
            next(createError(e.statusCode, e.message))
        }
    }
    static all = async (req, res, next) => {
        try {
            const users = await auth.all();
            res.status(200).json({
                status: true,
                message: 'All users',
                data: users
            })
        }
        catch (e) {
            next(createError(e.statusCode, e.message))
        }
    }
}
module.exports = authController;

创建一个授权卫士

在添加了控制器后,我们可以添加我们的卫兵,这将保护一些路由,如all ,防止未登录的用户。这个守护将验证我们发布的JWT令牌,如果有效,将允许用户访问这些路由。

创建一个名为middlewares/auth.js 的文件,并添加以下代码。

const jwt = require('../utils/jwt')
const createError = require('http-errors')
const auth = async (req, res, next) => {
    if (!req.headers.authorization) {
        return next(createError.Unauthorized('Access token is required'))
    }
    const token = req.headers.authorization.split(' ')[1]
    if (!token) {
        return next(createError.Unauthorized())
    }
    await jwt.verifyAccessToken(token).then(user => {
        req.user = user
        next()
    }).catch (e => {
        next(createError.Unauthorized(e.message))
    })
}
module.exports = auth;

上述代码将从routes 中添加的headers 中获取传递的令牌,以验证JWT令牌,并返回一个truefalse

创建一个授权路由

现在我们已经完成了我们的控制器、服务和防护。我们现在可以打开我们的routes/auth.js 文件并添加我们的路由。

const router = require('express').Router();
const user = require('../controllers/auth.controller');
const auth = require('../middlewares/auth');
// register
router.post('/', user.register);
// login
router.post('/login', user.login);
// all users
router.get('/', auth, user.all);
module.exports = router;

auth 防护被添加到all 路由中,以限制没有JWT令牌的用户进入该路由。

测试

现在我们已经完成了应用程序的构建,我们可以测试一下它是否工作正常。我们将使用Postman来测试registerloginall 路由。

Register

Screenshot of successful register test in Postman

从上面的Postman截图中可以看出,一旦你输入了你的电子邮件、姓名和密码,你就成功地注册为一个新用户。

Login

Screenshot of successful login test on Postman

当用户提供了正确的电子邮件和密码后,他们会得到一个访问令牌,他们将用来登录。在需要JWT令牌的请求中,这将作为一个头来传递。

All users

Screenshot of successful all users test on Postman

all 路径是一个受保护的路径,只针对拥有有效令牌的用户。正如你在上面的截图中看到的,令牌被添加到头的属性BearerToken

总结

在本教程中,我们已经经历了使用Prisma建立一个认证的服务器端应用程序的过程,用于数据库模式和迁移。然后,使用迁移后的数据,我们可以注册和登录一个用户,并创建接受有效令牌的保护路由。

对于更详细的代码库,你可以克隆存储库并进行配置。

The postCrafting authentication schemes with Prisma in Expressappeared first onLogRocket Blog.