使用Koa和Passport进行身份验证

4

我正在使用koa和passport尝试实现中间件,以防止未经身份验证的用户访问URI。

var koa = require('koa');
var session = require('koa-generic-session');
var bodyParser = require('koa-bodyparser');
var koaRouter = require('koa-router');
var passport = require('koa-passport');
var views = require('co-views');
var render = views('.', { map: { html: 'swig' }});
var localStrategy = require('passport-local').Strategy;

var app = koa();
var router = koaRouter();

app.keys = ['secret'];
app.use(session());
app.use(bodyParser());
app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser(function(user, done) {
    done(null, user);
});

passport.deserializeUser(function(user, done) {
    done(null, user);
});

passport.use(new localStrategy(function(username, password, done) {
    if (username === 'user1' && password === 'password2') {
        done(null, { userId: 99, userName: 'redBallons' });
    } else {
        done(null, false);
    }
}));

router.get('/login', function *(next) {
    this.body = yield render('index.html');
});

router.post('/login', passport.authenticate('local', {
    successRedirect: '/secretBankAccount',
    failureRedirect: '/login'
}));

router.get('*', function *(next) {
    if (! this.isAuthenticated()) {
        console.log('not authenticated');
        this.redirect('/login');
    } else {
        console.log('authenticated');
        yield next;
    }
});

router.get('/secretBankAccount', function *(next) {
    this.body = '2 dollars';
});

app.use(router.routes());
app.listen(8080);

然而,我始终无法进入我的secretBankAccount。我可以输入正确的用户名和密码并看到已通过验证的消息,但是在router.get('*')中的yield next()没有让我通过到下一个路由函数。

2个回答

6
使用koa-router时,预期只会命中一个路由。因此,即使您使用yield next,也不会再命中其他路由,包括'*'路由。

因此,您应该使用自己的身份验证中间件替换通用路由:

app.use(function*(next) {
  if (this.isAuthenticated()) {
    yield next
  } else {
    this.redirect('/login')
  }
});

身份验证中间件会强制你使用两个路由对象而不是一个来进行路由。这样你就可以区分公共路由和安全路由。因此,像这样的内容:
var public = new koaRouter();

public.get('/login', function *(next) {
    this.body = yield render('index.html');
});

public.post('/login', passport.authenticate('local', {
    successRedirect: '/secretBankAccount',
    failureRedirect: '/login'
}));

app.use(public.routes());

app.use(function*(next) {
  if (this.isAuthenticated()) {
    yield next;
  } else {
    this.redirect('/login');
  }
})

var secured = new koaRouter();

secured.get('/secretBankAccount', function *(next) {
  this.body = '2 dollars';
});

app.use(secured.routes());

在上面的例子中,请求将首先经过公共路由中间件。然后,如果它没有与公共路由匹配当前请求,它将移动到身份验证中间件。如果isAuthenticated()false,则会发生重定向。如果isAuthenticated()true,则会继续进行安全路由。
这种方法基于koa-passport-example项目,该项目是koa-passport作者创建的。

谢谢。有一个更正,路由器中间件 app.use(function*(next) 中需要添加一个检查以确保它不是 '/login' 路由。似乎当重定向完成后,路由器中间件会再次被调用。 - mfc
@mfc 重定向将始终强制调用路由中间件,因为这是重定向的本质(即客户端发送另一个HTTP请求)。如果这不是您首选的方法,您可以只发送HTTP状态401(未经授权),而不是重定向,并在客户端处理失败的请求。 - Peadar Doyle

1

peadar-doyle的回答可以实现这个功能,但需要更新以避免警告:koa deprecated Support for generators will be removed in v3.

这是更新后的版本。我发送401而不是重定向:

// all requests must now be authenticated
app.use(async (ctx, next) => {
    if (ctx.isAuthenticated()) {
        await next();
    } else {
        ctx.body = "access denied";
        ctx.status = 401;
    }
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接