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