80447704 2019-06-25
做后端系统避免不了要做权限认证,比如本地用户登录,第三方登录。
权限认证的思路也极其简单,不外乎就是登录,登出,路由守护三部分。
那么有没有现成的轮子可用呢?答案是肯定的,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
对象挂载以下方法与属性
另外附上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验证的话还需要另外编写策略才行。
更多详细用法,请自行到官网查看文档。