koa2 使用passport权限认证中间件

14

做后端系统避免不了要做权限认证,比如本地用户登录,第三方登录。
权限认证的思路也极其简单,不外乎就是登录,登出,路由守护三部分。

那么有没有现成的轮子可用呢?答案是肯定的,node发展了这么迅速,各种npm包层出不穷,总有那么几款厉害的。
今天要讲的权限认证中间件那就是:passport

passport目前有很多已经写好的登录策略,比如github登录,微信登录,Facebook登录,google等等。

官网 http://passportjs.org/docs/

官网是英文的,英文差的话不建议看了,去找个demo撸起来才是正确的学习思路。

通过一阵摸索,本文决定记录下koa2具体的使用步骤。

安装包

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

npm install -S koa-passport

代码

先来看代码,稍后再做解释。
这里使用 passport-local 策略(本地权限认证)为例子。
因为passport使用之前要定义策略及序列化与反序列化操作,所以把 passport 的配置及策略写到一个文件passport.js

定义策略

// passport.js
const passport = require('koa-passport')
var LocalStrategy = require('passport-local').Strategy


// 序列化ctx.login()触发
passport.serializeUser(function(user, done) {
  console.log('serializeUser: ', user)
  done(null, user.id)
})
// 反序列化(请求时,session中存在"passport":{"user":"1"}触发)
passport.deserializeUser(async function(id, done) {
  console.log('deserializeUser: ', id)
  var user = {id: 1, username: 'admin', password: '123456'}
  done(null, user)
})
// 提交数据(策略)
passport.use(new LocalStrategy({
  // usernameField: 'email',
  // passwordField: 'passwd'
}, function(username, password, done) {
  console.log('LocalStrategy', username, password)
  var user = {id: 1, username: username, password: password}
  done(null, user, {msg: 'this is a test'})
  // done(err, user, info)
}))


module.exports = passport

记得文件末 module.exports = passport 导出 passport

入口载入

然后在 koa 入口 app.js 中载入 passport.js 文件

const passport = require('./passport')

并在适当位置(看下边 app.js)使用passport中间件

app.use(passport.initialize())
app.use(passport.session())

passport 中间件需要用到 session ()所以,你的app.js入口文件类似这样

// app.js
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const static = require('koa-static')
const session = require('koa-session')
const RedisStore = require('koa-redis')
const app = new Koa()


const passport = require('./libs/passport')
const baseConf = require('./config/base')
const redisConf = require('./config/redis')

// 基础中间件
app.use(async (ctx, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  console.log(`${ctx.method} ${ctx.status} ${ctx.url} - ${ms} ms`)
})


app.keys = ['123456']
app.use(bodyParser())
app.use(session({
  cookie: {secure: false, maxAge:86400000},
  store: RedisStore(redisConf.session)
}, app))


app.use(passport.initialize())
app.use(passport.session())

var router = require('./routes')

router.all('404', '*', ctx => {
  ctx.status = 404
  ctx.body = '404'
})

app.use(router.routes())
app.use(router.allowedMethods())

var log = require('./libs/log')
app.on('error', (err, ctx) => {
  log.error(`${ctx.method} ${ctx.url}`, 'Error: ')
  log.error(err)
  console.log(err)
})

app.listen(baseConf.port)
console.log('listening on ' + baseConf.port)
module.exports = app

编写路由

编写路由及守护中间件。

  • 登录
POST /login
router.post('/login', ctx => {
  // 会调用策略
  return passport.authenticate('local',
    function(err, user, info, status) {
      ctx.body = {user, err, info, status}
      return ctx.login({id: 1, username: 'admin', password: '123456'})
    })(ctx)
})
  • 登出
GET /logout
router.get('/logout', ctx => {
  ctx.logout()
  ctx.body = {auth: ctx.isAuthenticated(), user: ctx.state.user}
})
  • 路由守护中间件

比如你/api/*的路由需要用户认证才能访问

router.use('/api/*', (ctx, next) => {
   if(ctx.isAuthenticated()) {
     next()
   } else {
    ctx.status = 401
    ctx.body = {
      msg: 'auth fail'
    }
  }
})

到这里,本地权限认证基本完成了,post请求 /login 并且提交表单username,和 password即可登录一个用户。

/logout 退出当前登录。

解释

使用 passport 这个中间件,必须了解其运行步骤和知识点。

passport 以策略来扩展验证,什么是策略呢?

比如:本地策略,github登录策略,微信登录策略

passport 中间件使用前,需要注册策略,及实习序列化与反序列化操作。

序列化

通过 passport.serializeUser 函数定义序列化操作。

// 序列化
passport.serializeUser(function(user, done) {
  done(null, user.id)
})

在调用 ctx.login() 时会触发序列化操作。

反序列化

通过 passport.deserializeUser 函数定义反序列化操作。

// 反序列化
passport.deserializeUser(async function(id, done) {
  console.log('deserializeUser: ', id)
  var user = {id: 1, username: 'admin', password: '123456'}
  done(null, user)
})

在请求时,session中如果存在 "passport":{"user":"xxx"}时会触发定义的反序列化操作。

注册策略

// 策略
passport.use(new LocalStrategy({
  // usernameField: 'email',
  // passwordField: 'passwd'
}, function(username, password, done) {
  var user = {id: 1, username: username, password: password}
  done(null, user)
}))

在使用 passport.authenticate('策略', ...) 的时候,会执行策略

其他

app.use(passport.initialize()) 会在请求周期ctx对象挂载以下方法与属性

  • ctx.state.user 认证用户
  • ctx.login(user) 登录用户(序列化用户)
  • ctx.isAuthenticated() 判断是否认证

github

另外附上github的认证代码

安装包

npm install -S passport-github

passport.js载入

var GitHubStrategy = require('passport-github').Strategy

passport.js 增加代码

passport.use(new GitHubStrategy({
    clientID: githubConf.clientId,
    clientSecret: githubConf.secret,
    callbackURL: githubConf.callback
  },
  function(accessToken, refreshToken, profile, done) {
    // console.log(accessToken, refreshToken, profile)
    return done(null, {accessToken, refreshToken, profile})
  }
))

添加两个路由

// 调用授权页面
router.get('/auth/github', ctx => {
  return passport.authenticate('github', {scope: ['user:email']})(ctx)
})
// 授权回调得到code
router.get('/auth/github/callback', async ctx => {
  return passport.authenticate('github', (err, user, info, status) => {
    ctx.body = {err, user, info, status}
    return ctx.login(user)
  })(ctx)
})

以上例子只是模拟,并没有涉及数据库的操作,具体的实现还需要自己按照业务需求实现。

passport使用session来维护会话。对于token验证的来说,并不能用,所以要实现token验证的话还需要另外编写策略才行。

更多详细用法,请自行到官网查看文档。


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

李想 · 2018年08月16日
router.use('/api/*', async (ctx, next) => {
   if(ctx.isAuthenticated()) {
     await next()
   } else {
    ctx.status = 401
    ctx.body = {
      msg: 'auth fail'
    }
  }
})

只要有异步中间件,所有的中间件都要改成异步执行,否则同步代码会过早抛出错误。

回复

Renkosky · 2018年12月17日

序列化反序列化是什么意思啊

回复

0

这里序列化指的是把用户对象存到session里,反序列化就是反过来,从session里取用户数据成对象,session 可能是存数据库的或者写文件里的,像php默认是写文件的,koa-session 的好像是基于内存的。

mengdu 作者 · 2018年12月17日
0

@mengdu 感谢!我其实还有一个问题。。https://segmentfault.com/q/10... 麻烦你了。。ctx中没有login这个函数,我明明在app.js里面调用了app.use(passport.initialize())的,,

Renkosky · 2018年12月17日
载入中...