使用 Hapi 和 Passport.js 实现多种认证策略

随着 Web 技术的发展,用户认证已成为前端领域中非常重要的一环。在前端开发中,常常需要实现多种认证策略,比如本地认证、第三方认证(GitHub、Facebook等)、单点登录等等。本文将介绍使用 Hapi 和 Passport.js 实现多种认证策略的方法及示例代码。

Hapi 能力简介

Hapi 是一个万能的 Node.js Web 框架,支持多种 Web 开发场景。Hapi 具有以下优点:

  • 多样化的路由 策略:支持自定义路由策略,在安全和性能方面有很大的优势。
  • 配置简单:Hapi 框架非常简单易用,可以让你快速搭建一个实用的 Web 系统。
  • 插件管理:支持多种插件,能够满足各种需求,扩展性非常好。

Passport.js 能力简介

Passport.js 是一个非常流行的 Node.js 认证库,常常用于 Web 开发中的身份验证。Passport.js 的主要优点有:

  • 多种认证策略:提供本地认证、GitHub 等第三方认证以及单点登录等多种认证方式。
  • 可配置性强:可以方便地自定义策略和创建可重用的策略。
  • 扩展性强:具有许多插件,可以很方便地添加新的策略支持。

实现多种认证策略

假设我们需要实现本地认证和 GitHub 认证两种策略。下面我们来逐步完成认证的操作。

  1. 安装 Hapi 和 Passport.js
npm install hapi passport passport-local passport-github
  1. 定义本地策略
const passport = require('passport');
const LocalStrategy = require('passport-local'). Strategy;
passport.use(
  new LocalStrategy(
    {
      usernameField: 'username',
      passwordField: 'password'
    },
    (username, password, done) => {
      if (username === 'admin' && password === 'admin') {
        return done(null, { username: 'admin' });
      } else {
        return done(null, false, '用户名或密码错误');
      }
    }
  )
); 

在上面的代码中,我们使用 passport-local 模块的 LocalStrategy 策略来实现本地认证。在 LocalStrategy 构造函数中,我们传入了一个包含 usernameFieldpasswordField 的配置对象。这里的 usernamepassword 就是从前端传递进来的用户信息。如果用户名和密码正确,就返回用户信息,否则返回 false 和提示信息。

  1. 在 Hapi 中注册本地策略

在 Hapi 应用中注册 Passport.js,在路由处理之前调用 Passport.js 的 initialize() 方法和 session() 方法即可。通过调用 passport.authenticate('local') 进行本地认证,并在认证通过后调用 req.login() 方法存储用户信息。

const Hapi = require('hapi');
const passport = require('passport');
const server = new Hapi.Server();
server.connection({ port: 3000 });
server.register(require('hapi-auth-cookie'), err => {
  server.auth.strategy('session', 'cookie', {
    cookie: {
      password: 'password-should-be-32-characters',
      isSecure: false
    },
    redirectTo: '/login',
    redirectOnTry: false,
    appendNext: true,
    ttl: 24 * 60 * 60 * 1000
  });
  server.register(require('hapi-auth-basic'), err => {
    server.auth.strategy('simple', 'basic', { validateFunc: (request, username, password, callback) => callback(null, true, { username, password }) });
  });
  server.register(require('bell'), err => {
    server.auth.strategy('github-oauth', 'bell', {
      provider: 'github',
      password: 'password-should-be-32-characters',
      clientId: '<client-id>',
      clientSecret: '<client-secret>',
      isSecure: false,
      location: 'http://localhost:3000'
    });
  });
  server.register(require('vision'), err => {
    server.views({
      engines: {
        html: require('handlebars')
      },
      path: __dirname + '/views',
      layout: 'layouts/main',
      partialsPath: 'views/partials'
    });
  });
  server.register(require('inert'), err => {});
  server.ext('onPreResponse', function(request, reply){
    if (request.response.isBoom) {
      const { statusCode, payload } = request.response.output;
      if (statusCode === 401) {
        console.log(payload.message);
      }
    }
    return reply.continue();
  });
  server.route([
    {
      method: 'GET',
      path: '/login',
      config: {
        auth: false,
        handler: (request, reply) => {
          return reply.view('login');
        }
      }
    },
    {
      method: 'POST',
      path: '/local',
      config: {
        auth: {
          strategy: 'session',
          mode: 'try'
        },
        handler: (request, reply) => {
          passport.authenticate('local', (err, user, message) => {
            if (err || !user) {
              return reply({ message });
            }
            request.login(user, err => {
              if (err) {
                return reply(err);
              }
              return reply({ message: '认证成功' });
            });
          })(request, reply);
        }
      }
    },
    {
      method: 'GET',
      path: '/github',
      config: {
        auth: 'github-oauth',
        handler: (request, reply) => {
          if (!request.auth.isAuthenticated) {
            return reply('认证失败');
          }
          return reply.redirect('/');
        }
      }
    },
    {
      method: 'GET',
      path: '/me',
      config: {
        auth: 'session',
        handler: (request, reply) => {
          reply(request.auth.credentials);
        }
      }
    }
  ]);
  server.start(err => {
    if (err) {
      console.log(err);
    }
    console.log(`Server running at: ${server.info.uri}`);
  });
});

在上面的代码中,我们通过 hapi-auth-cookie 插件注册了 Passport.js,同时为认证设置了 cookie。通过调用 server.auth.strategy() 方法并传入插件名称和策略名称,完成了身份认证的注册。

在路由处理中,为 GET /login 注册了不需要认证的的策略。并创建了用于处理本地认证的 POST /local 路由。在 POST /local 中,首先使用 LocalStrategy 进行本地认证,如果认证通过,就使用 req.login() 方法存储用户信息。最后我们定义了 GET /me,该路由需要使用 session 策略进行认证,返回用户信息。GET /github 是用于处理 GitHub 认证的路由,需要使用 bell 插件进行认证。

  1. 在 Hapi 中注册 GitHub 认证

首先在 GitHub 中创建应用,并获取 clientId 和 clientSecret。随后在服务器端注册策略并在路由中使用该策略进行认证。在下面的代码中,我们使用 passport-github 模块的 GitHubStrategy 策略实现 GitHub 认证,通过调用 server.auth.strategy('github-oauth') 方法,在服务启动时注册 GitHub 策略。最后在 GET /github 路由中使用 bell 插件的 bell.authenticate() 方法进行认证。

  1. 测试

我们可以使用 POSTMAN 进行测试。当请求 POST /local 时,传入如下请求体:

返回如下信息,表示本地认证成功:

当请求 GET /me 时,返回认证用户的信息:

当请求 GET /github 时,跳转到 GitHub 认证页面,认证成功后会重定向到 / 页面,认证失败则会返回“认证失败”信息。

总结

通过本文的介绍,我们学习了如何使用 Hapi 和 Passport.js 实现多种认证策略。当然,认证策略不止本地认证和 GitHub 认证,我们可以根据需求来自定义策略。总之,了解并掌握认证是前端开发中非常重要的一环,同时也是一项必备技能。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/659f6f1badd4f0e0ff80e823


纠错反馈