Hapi-js实现基于jwt的注册登录和验证

1,729 阅读4分钟

本文介绍如何用Hapi构建一个简单的基于jwt的用户注册登录和验证的应用。

Hapi是一个开源的、基于Node.js的应用框架,它适用于构建应用程序和服务,其设计目标是让开发者把精力集中于开发可重用的应用程序的业务逻辑,向开发者提供构建应用程序业务逻辑所需的基础设施。

hapi 是由沃尔玛技术团队开发的 Web 框架,其优势在于:

  • 高性能 - hapi 的开发者遵守 Benchmark Driven Development
  • 安全性高,轻量级
  • 可扩展性
  • 内置缓存,Redis、 MongoDB、Memcached
  • 核心代码做 100% 测试
  • 内置端到端测试
  • 核心功能内置,其他特性以插件的形式展现。

因此,即便是在黑色星期五这样的的抢购活动中,hapi 的表现也十分优异,我们也可以利用 hapi 高效的创建出一系列支持 RESTful 规范的接口。

一、创建项目

  • 项目结构
项目文件结构
➜  hapi mkdir userLogin
➜  hapi cd userLogin
➜  userLogin npm init -y
Wrote to /Users/huangyuhui/Desktop/hapi/userLogin/package.json:

{
  "name": "userLogin",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

二、安装依赖

➜  userLogin npm install hapi mongoose nodemon jsonwebtoken bcrypt

三、添加启动脚本

修改packge.json文件,在"scripts"对象中添加代码:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon server.js"
  },

四、创建用户模型

  • 在项目根目录创建models文件夹
  • models文件夹下创建User.js文件
const mongoose = require('mongoose')
const Schema = mongoose.Schema

// Create Schema
const UserSchema = new Schema({
  first_name: {
    type: String
  },
  last_name: {
    type: String
  },
  email: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  date: {
    type: Date,
    default: Date.now
  }
})

module.exports = User = mongoose.model('users', UserSchema)

五、创建服务

  • 在项目根目录创建server.js文件
'use strict'

const Hapi = require('hapi') //引入hapijs
const mongoose = require('mongoose') //操作mongoDB数据库

//创建服务器
const server = new Hapi.Server({
  host: 'localhost',
  port: 5000,
  //跨域设置
  routes: {
    cors: true
  }
})

//连接本地mongoDB数据库
server.app.db = mongoose.connect(
  'mongodb://localhost/mernloginreg', {
    useNewUrlParser: true,
    useUnifiedTopology: true
  }
)

//定义服务启动函数
const init = async () => {
  await server
    //注册路由
    .register({
      plugin: require('./routes/Users')
    }, {
      routes: {
        //设置路由默认前缀
        prefix: '/users'
      }
    })
    .catch(err => {
      console.log(err)
    })

  //启动服务
  await server.start()
  //提示服务启动信息
  console.log(`Server running at: ${server.info.uri}`)
}

init()

六、创建路由插件

  • 在项目根目录创建routes文件夹
  • routes文件夹下创建Users.js文件
'use strict'
const jwt = require('jsonwebtoken') //token签发与验证
const bcrypt = require('bcrypt') //字符串加密
const User = require('../models/User') //导入用户模型
const mongoose = require('mongoose') //操作mongoDB数据库

process.env.SECRET_KEY = 'secret' //设置密钥


//自定义路由插件
exports.plugin = {
  register: (server, options, next) => {
    //定义用户注册接口
    server.route({
        method: 'POST',
        path: '/register',
        handler: (req, h) => {
          //设置注册时间
          let today = new Date()
          let m = today.getMinutes() - today.getTimezoneOffset()
          today = today.setMinutes(m)

          //从http请求中取出参数构建用户对象
          const userData = {
            first_name: req.payload.first_name,
            last_name: req.payload.last_name,
            email: req.payload.email,
            password: req.payload.password,
            date: today
          }

          //查找数据库中是否有相同的email字段内容
          return User.findOne({
              email: req.payload.email
            })
            .then(user => {
              //如果没有找到,说明是新注册用户
              if (!user) {
                //对客户端输入的密码明文进行加密
                bcrypt.hash(req.payload.password, 10, (err, hash) => {
                  userData.password = hash
                  //将注册用户信息写入数据库
                  return User.create(userData)
                    .then(user => {
                      //返回注册成功信息
                      return {
                        status: user.email + ' Registered!'
                      }
                    })
                    .catch(err => {
                      //返回错误信息
                      return 'error: ' + err
                    })
                })
                //返回用户注册内容
                return userData
              } else {
                //如果数据库中有相同的email内容,返回用户已经存在,不再重复写入。
                return {
                  error: 'User already exists'
                }
              }
            })
            .catch(err => {
              //错误信息提示
              return 'error: ' + err
            })
        }
      }),
      //定义用户登录接口
      server.route({
        method: 'POST',
        path: '/login',
        handler: (req, h) => {
          //根据请求的email内容查找数据库记录
          return User.findOne({
              email: req.payload.email
            })
            .then(user => {
              if (user) {
                //如果存在请求的email内容,继续通过比较请求的密码值
                if (bcrypt.compareSync(req.payload.password, user.password)) {
                  //如果请求的密码与数据库中记录的密码值相同
                  //定义欲签发token的内容对象
                  const payload = {
                    id: user._id,
                    first_name: user.first_name,
                    last_name: user.last_name,
                    email: user.email
                  }
                  //签发token
                  let token = jwt.sign(payload, process.env.SECRET_KEY, {
                    //设置token有效期
                    expiresIn: 1440
                  })
                  //返回token字符串的值
                  return token
                } else {
                  //如果请求的用户密码不存在,返回用户不存在信息
                  return {
                    error: 'User does not exist'
                  }
                }
              } else {
                //如果请求的用户email不存在,返回用户不存在信息
                return {
                  error: 'User does not exist'
                }
              }
            })
            .catch(err => {
              return {
                error: err
              }
            })
        }
      }),

      //获取用户信息
      server.route({
        method: 'GET',
        path: '/profile',
        handler: (req, h) => {
          //验证请求头中的token值,还原token内容对象
          var decoded = jwt.verify(
            req.headers.authorization,
            process.env.SECRET_KEY
          )
          //根据token对象的id值查找数据库中的记录
          return User.findOne({
              _id: mongoose.Types.ObjectId(decoded.id)
            })
            .then(user => {
              console.log(user)
              //找到则返回请求查找的用户信息,否则返回找不到用户信息
              if (user) {
                return user
              } else {
                return 'User does not exist'
              }
            })
            .catch(err => {
              return 'error: ' + err
            })
        }
      })
  },
  name: 'users' //自定义插件的名称
}

七、接口测试

启动服务:

➜  userLogin npm start

> userLogin@1.0.0 start /Users/huangyuhui/Desktop/hapi/userLogin
> nodemon server.js

[nodemon] 2.0.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server.js`
Server running at: http://localhost:5000

  • 用户注册

  • 用户登录

  • 获取用户信息

掘金年度征文 | 2019 与我的技术之路 征文活动正在进行中......