当前位置: 首页 > 工具软件 > koa-proxy > 使用案例 >

koa-passport实现本地验证

毋修为
2023-12-01


最近用nuxt.js实现了本地注册登录相关操作,其中用到了koa-passport包,在这里记录下,方便后面回顾

passport中间件专门用来做权限认证
passport目前有很多已经写好的登录策略,比如github登录,微信登录,Facebook登录,google等

passport官网

安装包

koa2中使用的是 koa-passport 这个包。
本地验证用的是 passport-local这个策略

npm install koa-passport passport-local

代码

server\interface\utils\passport.js

import passport from 'koa-passport'
import LocalStrategy from 'passport-local'
import UserModel from '../../dbs/models/users'
passport.use(new LocalStrategy(async (username, password, done) => {
  let where = {
    username
  }
  let result = await UserModel.findOne(where)
  if (result != null) {
    if (result.password === password) {
      // 用户输入密码跟数据库密码一致
      return done(null, result)
    } else {
      return done(null, false, '密码错误')
    }
  } else {
    return done(null, false, '用户不存在')
  }
}))

// 在每次请求时 会从session中读取用户对象  用户通过验证后serializeUser会将用户数据存在session中
// 序列化  ctx.login()触发
passport.serializeUser((user, done) => {
  console.log('serializeUser:')
  console.log(user)
  done(null, user)
})
// 反序列化 (请求时,session中存在"passport":{"user":"1"}触发)
passport.deserializeUser((user, done) => {
  console.log('deserializeUser:')
  console.log(user)
  done(null, user)
})

export default passport

server\index.js

// const Koa = require('koa')
import Koa from 'koa'
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')

import mongoose from 'mongoose'
// 用来处理post请求相关 没有这个包就无法通过ctx.request.body获取post请求参数
import bodyParser from 'koa-bodyparser'
// 存session cookie相关的 包
import session from 'koa-generic-session'
import Redis from 'koa-redis'
// 解决服务端向客户端发json 格式美化
import json from 'koa-json'
// 数据库相关配置
import dbConfig from '../server/dbs/config'
import passport from './interface/utils/passport'
import users from '../server/interface/users'

const app = new Koa()

// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = app.env !== 'production'

async function start () {
  // Instantiate nuxt.js
  const nuxt = new Nuxt(config)

  const {
    host = process.env.HOST || '127.0.0.1',
    port = process.env.PORT || 3000
  } = nuxt.options.server

  app.keys = ['mt', 'keyskeys']
  app.proxy = true
  // 客户端存的是cookie 服务端是session
  app.use(session({
    key: 'mt',
    prefix: 'mt:uid',
    // session 相关存到redis
    store: new Redis()
  }))
  app.use(bodyParser({
    extendTypes: ['json', 'form', 'text']
  }))
  app.use(json())
  // 连接数据库
  mongoose.connect(dbConfig.dbs, {
    useNewUrlParser: true
  })
  // 处理登录相关
  app.use(passport.initialize())
  app.use(passport.session())

  await nuxt.ready()
  // Build in development
  if (config.dev) {
    const builder = new Builder(nuxt)
    await builder.build()
  }
  // 引入路由
  app.use(users.routes()).use(users.allowedMethods())

  app.use((ctx) => {
    ctx.status = 200
    ctx.respond = false // Bypass Koa's built-in response handling
    ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
    nuxt.render(ctx.req, ctx.res)
  })

  app.listen(port, host)
  consola.ready({
    message: `Server listening on http://${host}:${port}`,
    badge: true
  })
}
start()

server\interface\users.js

import Router from "koa-router";
import Redis from "koa-redis";
// nodemailer支持node发邮件
import nodeMailer from "nodemailer";
import User from "../dbs/models/users";
import Passport from "./utils/passport";
import Email from "../dbs/config";
import axios from "./utils/axios";

// 路由前缀
let router = new Router({
  prefix: "/users"
});

// 获取redis客户端
let Store = new Redis().client;

// 注册接口
router.post("/signup", async ctx => {
  const { username, password, email, code } = ctx.request.body;

  // 校验验证码 nodemailer发验证码时会存进redis里 这里从redis里拿出对比
  if (code) {
    // Store.hget(`nodemail:${username}`, 'code')  redis是key-value存储 nodemail表示属于哪个模块 username用来匹配    减值code
    const saveCode = await Store.hget(`nodemail:${username}`, "code");
    // 过期时间
    const saveExpire = await Store.hget(`nodemail:${username}`, "expire");
    if (code === saveCode) {
      if (new Date().getTime() - saveExpire > 0) {
        ctx.body = {
          code: -1,
          msg: "验证码已过期,请重新尝试"
        };
        return false;
      }
    } else {
      ctx.body = {
        code: -1,
        msgL: "请填写正确的验证码"
      };
    }
  } else {
    ctx.body = {
      code: -1,
      msg: "请填写验证码"
    };
  }

  let user = await User.find({
    username
  });

  if (user.length) {
    ctx.body = {
      code: -1,
      msg: "已被注册"
    };
    return;
  }

  // 写库操作
  let nuser = await User.create({
    username,
    password,
    email
  });
  console.log(username);
  if (nuser) {
    // 写库成功
    let res = await axios.post("/users/signin", {
      username,
      password
    });
    if (res.data && res.data.code === 0) {
      ctx.body = {
        code: 0,
        msg: "注册成功",
        user: res.data.user
      };
    } else {
      ctx.body = {
        code: -1,
        msg: "error"
      };
    }
  } else {
    ctx.body = {
      code: -1,
      msg: "注册失败"
    };
  }
});

// 登录接口
router.post("/signin", async (ctx, next) => {
  return Passport.authenticate("local", function(err, user, info, status) {
    if (err) {
      ctx.body = {
        code: -1,
        msg: err
      };
    } else {
      if (user) {
        ctx.body = {
          code: 0,
          msg: "登录成功",
          user
        };
        // ctx.login() 由passport提供
        return ctx.login(user);
      } else {
        // 异常
        ctx.body = {
          code: 1,
          msg: info
        };
      }
    }
  })(ctx, next);
});

// 验证码验证
router.post("/verify", async (ctx, next) => {
  let username = ctx.request.body.username;
  const saveExpire = await Store.hget(`nodemail:${username}`, "expire");
  console.log("username:" + username);
  console.log("saveExpire:" + saveExpire);
  console.log(new Date().getTime() - saveExpire);
  if (saveExpire && new Date().getTime() - saveExpire < 0) {
    ctx.body = {
      code: -1,
      msg: "验证请求过于频繁,1分钟内1次"
    };
    return false;
  }

  // 验证邮件相关
  // 发送对象
  let transporter = nodeMailer.createTransport({
    host: Email.smtp.host,
    port: 587,
    // secure: true 则监听405端口 false 为其他端口
    secure: false,
    // 权限校验
    auth: {
      user: Email.smtp.user,
      pass: Email.smtp.pass
    }
  });
  // 接手对象
  let ko = {
    code: Email.smtp.code(),
    expire: Email.smtp.expire(),
    email: ctx.request.body.email,
    user: ctx.request.body.username
  };

  // 邮件显示内容
  let mailOptions = {
    // 标题
    from: `"认证邮件" <${Email.smtp.user}>`,
    to: ko.email,
    // 主题
    subject: "《慕课网高仿美团网全栈实战》注册码",
    html: `您在《慕课网高仿美团网全栈实战》课程中注册,您的邀请码是${ko.code},对应用户名${ko.user}`
  };
  // 发送邮件
  await transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      return console.log("error");
    } else {
      // 存到redis哈希表里
      Store.hmset(
        `nodemail:${ko.user}`,
        "code",
        ko.code,
        "expire",
        ko.expire,
        "email",
        ko.email
      );
    }
  });
  ctx.body = {
    code: 0,
    msg: "验证码已发送,可能会有延时,有效期1分钟"
  };
});

// 退出接口
router.get("/exit", async (ctx, next) => {
  // ctx.logout() 由passport包提供
  await ctx.logout();
  // isAuthenticated 这个API由passport包提供
  if (!ctx.isAuthenticated()) {
    // 检查当前是不是登录状态 不是的话 说明退出成功
    ctx.body = {
      code: 0
    };
  } else {
    ctx.body = {
      code: -1
    };
  }
});

// 获取用户名
router.get("/getUser", async ctx => {
  // isAuthenticated 这个API由passport包提供  user也是登录成功时passport这个包存进session里的
  if (ctx.isAuthenticated()) {
    const { username, email } = ctx.session.passport.user;
    ctx.body = {
      user: username,
      email
    };
  } else {
    ctx.body = {
      user: "",
      email: ""
    };
  }
});

// 导出路由
export default router;

解释

passport这个中间件,
passport 通过策略来扩展验证
比如:本地策略,github登录策略,微信登录策略
passport 中间件使用前,需要注册策略,以及序列化反序列化操作

通过passport.serializeUser函数定义序列化操作,调用ctx.login()会触发序列化操作

通过passport.deserializeUser函数定义反序列化操作,在session中如果存在passport会触发反序列化操作

通过passport.use(new LocalStrategy('local', ...))注册策略,
调用passport.authenticate('local',...)调用策略

app.use(passport.initialize())会在ctx挂载以下方法

ctx.state.user认证用户

ctx.login(user)登录用户

ctx.logout()用户退出登录

ctx.isAuthenticated()判断是否认证
ctx.session.passport.user获取用户对象

 类似资料: