passport中间件专门用来做权限认证
passport目前有很多已经写好的登录策略,比如github登录,微信登录,Facebook登录,google等
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
获取用户对象