Passport.js 是专为 Node.js 设计的轻量级身份验证中间件。

  • 灵活性极高:支持大量不同类型的身份验证策略,包含传统的本地用户名与密码验证,以及第三方登录(如 GitHub、Google、Facebook 等基于 OAuth 或 OpenID 协议的登录方式)。这使得开发者可以根据项目的实际需求,轻松选择合适的验证方式,无论是面向传统用户的系统,还是希望集成第三方登录来简化用户注册流程的应用都能适用。
  • 良好的集成性:可轻松与各种流行的 Web 框架集成,像 Express、Koa 等。它如同一个可插拔的组件,开发者能在框架搭建好的基础上,无缝接入 Passport.js 来添加身份验证功能,不会对现有框架的核心逻辑造成太大干扰 。
  • 模块化设计:通过不同的策略模块实现各种认证方式。这意味着如果项目只需要本地认证,那么只需引入 passport-local 模块;若还想支持 GitHub 登录,单独引入 passport-github 模块即可,使得代码结构清晰,可维护性强。

准备工作

首先需要安装passport库,本文使用koa框架来进行演示,可以使用koa-passport代替passport

$ pnpm install koa koa-passport
$ pnpm install express passport

这里我们使用 sqlite 来做数据持久化,先安装一下驱动,然后添加sequelize来通过ORM读写数据

$ pnpm install sqlite3 sequelize

来创建一个User Model

import { Model, DataTypes } from 'sequelize';
import bcrypt from 'bcrypt';
import sequelize from '../db';

class User extends Model {
  public id!: number;
  public username!: string;
  public password!: string;

  public async comparePassword(password: string): Promise<boolean> {
    return bcrypt.compare(password, this.password);
  }
}

User.init({
  id: {
    type: DataTypes.INTEGER,
    autoIncrement: true,
    primaryKey: true,
  },
  username: {
    type: new DataTypes.STRING(128),
    allowNull: false,
    unique: true,
  },
  password: {
    type: new DataTypes.STRING(128),
    allowNull: false,
  },
}, {
  tableName: 'users',
  sequelize,
  hooks: {
    beforeCreate: async (user: any) => {
      if (user.password) {
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(user.password, salt);
      }
    },
    beforeUpdate: async (user: any) => {
      if (user.changed('password')) {
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(user.password, salt);
      }
    },
  },
});

export default User;

创建数据库连接

import { Sequelize } from 'sequelize';

const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: './database.sqlite'
});

export default sequelize;

local 策略

安装自己所需的验证策略,先使用local策略来试一下

$ pnpm install passport-local

给passport添加local策略,顺便添加一下用户信息的序列化和反序列化

import passport from 'koa-passport';
import { Strategy as LocalStrategy } from 'passport-local';
import User from '../models/User';

passport.serializeUser((user: any, done) => {
  done(null, user.id);
});

passport.deserializeUser(async (id: number, done) => {
  try {
    const user = await User.findByPk(id);
    done(null, user);
  } catch (err) {
    done(err);
  }
});

passport.use(new LocalStrategy({ usernameField: 'username' }, async (username, password, done) => {
  try {
    const user = await User.findOne({ where: { username } });
    if (!user) {
      return done(null, false, { message: 'Incorrect username.' });
    }
    const isMatch = await user.comparePassword(password);
    if (!isMatch) {
      return done(null, false, { message: 'Incorrect password.' });
    }
    return done(null, user);
  } catch (err) {
    return done(err);
  }
}));

现在我们接触到了pasport的策略配置API

  • use:添加要使用的策略。
  • serializeUser:序列化用户信息,将序列化的用户信息保存在sesion中,用于鉴权时使用。
  • deserializeUser:反序列化用户信息,根据序列化的数据将用户信息写入请求上下文。
    上面的代码已经为passport添加了local策略,下面我们来创建路由,并使用passport中间件进鉴权处理。
    安装koa路由中间件
$ pnpm install @koa/router

添加路由声明如下

import Router from '@koa/router';
import passport from 'koa-passport';

const router = new Router();

router.get('/login', async (ctx) => {
  if (ctx.isAuthenticated()) {
    return ctx.redirect('/profile');
  }
  await ctx.render('login');
});

router.get('/register', async (ctx) => {
  await ctx.render('register');
});

router.post('/register', async (ctx) => {
  const { username, password } = ctx.request.body as any;
  try {
    const user = await User.create({ username, password });
    await ctx.login(user);
    ctx.redirect('/profile');
  } catch (error) {
    ctx.status = 400;
    ctx.body = { message: 'Registration failed', error };
  }
});

router.post('/login', passport.authenticate('local', {
  successRedirect: '/profile',
  failureRedirect: '/login',
  failWithError: true
}));

router.post('/logout', (ctx) => {
  ctx.logout();
  ctx.redirect('/login');
});

router.get('/profile', async (ctx) => {
  if (ctx.isAuthenticated()) {
    await ctx.render('profile', { user: ctx.state.user });
  } else {
    ctx.redirect('/login');
  }
});

export default router;

(页面使用ejs模板引擎进行渲染,这里就不贴ejs源文件代码了,查看demo仓库
这里我们接触了passport的校验API

  • isAuthenticated:判读该用户是否在session数据中。
  • authenticate:验证用户身份,根据指定的策略进行鉴权。
    此时我们通过浏览器访问刚才的几个页面可以看到效果如下:
    passport.gif
    到此最基本的基于local策略的passport鉴权已经实现了。

OAuth2策略(Github为例)

首先需要安装github策略

$ pnpm install passport-github

然后添加 github 策略到passport中

import passport from 'koa-passport';
import { Strategy as GitHubStrategy } from 'passport-github2';
import dotenv from 'dotenv';
import User from '../models/User';

dotenv.config();

passport.use(new GitHubStrategy({
  clientID: process.env.GITHUB_CLIENT_ID as string,
  clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
  callbackURL: 'http://localhost:3000/auth/github/callback'
}, async (accessToken: any, refreshToken: any, profile: any, done: any) => {
  try {
    const [user, created] = await User.findOrCreate({
      where: { username: profile.username },
      defaults: { password: '' } // No password for OAuth users
    });
    return done(null, user);
  } catch (err) {
    return done(err);
  }
}));

🔔:注意不要泄漏自己的clentIdclentSecret,这里我通过env配置文件添加到环境变量中了。
然后为github策略添加路由

import Router from '@koa/router';
import passport from 'koa-passport';
import dotenv from 'dotenv';
import User from '../models/User';

dotenv.config();

const router = new Router();

router.get('/auth/github', passport.authenticate('github', { scope: ['user:email'] }));

router.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (ctx) => {
  ctx.redirect('/profile');
});

export default router;

此时我们点击Login with GitHub即可跳转至Github的授权页面
image.png
同意授权之后就可以回到刚才的profile页面
image.png

PassportJS 的接入方式就是如此的简单。
留一个小作业:在官方策略库中还有更多的鉴权策略,可以自行尝试一下感兴趣的策略,来验证一下是否掌握了Passport的接入方式。


前端小白