再说一次passport.js

最近,他们为我提供了一个express.js项目的支持。 在研究项目代码时,我发现认证/授权工作有些混乱,该认证/授权(例如99.999%的案例)是基于password.js库的。 该代码有效,并且遵循“工作-请勿触摸”的原则,我将其保留为原样。 几天后,我被赋予添加两个授权策略的任务。 然后,我开始回想起我已经在做类似的工作,并且花费了几行代码。 浏览了passport.js上的文档后,我几乎没有动静地理解如何操作,因为 在那里,他们考虑了仅使用一种策略的情况,并针对每种策略分别给出了示例。 但是,如何结合几种策略,为什么需要使用logIn()方法(与login()相同)-尚不清楚。 因此,为了立即了解,而不是一次又一次地重复相同的搜索,我为自己编写了这些注释。

有点历史。 最初,Web应用程序使用两种身份验证/授权:1)基本和2)使用cookie的会话。 在基本身份验证/授权中,在每个请求中发送标头,因此在每个请求中执行客户端身份验证。 使用会话时,客户端身份验证仅执行一次(方法可能非常不同,包括Basic以及以名称和密码形式发送的形式,以及成千上万的其他方法(就passport.js而言称为策略))。 最主要的是,在身份验证之后,客户端会在Cookie中标识会话标识符(或在某些实现中为会话数据),而用户标识符将存储在会话数据中。

首先,您需要确定是否将应用程序中的会话用于身份验证/授权。 如果您正在为移动应用程序开发后端,那么很可能不会。 如果这是一个Web应用程序,则很可能是。 要使用会话,您需要激活cookie解析器,会话中间件并初始化会话:

const app = express(); const sessionMiddleware = session({ store: new RedisStore({client: redisClient}), secret, resave: true, rolling: true, saveUninitialized: false, cookie: { maxAge: 10 * 60 * 1000, httpOnly: false, }, }); app.use(cookieParser()); app.use(sessionMiddleware); app.use(passport.initialize()); app.use(passport.session()); 

在这里,您需要给出一些重要的解释。 如果您不希望Redis在几年内吃光所有RAM,则需要注意及时删除会话数据。 maxAge参数对此负责,它为cookie和存储在redis中的值均等地设置了该值。 设置值resave:true,rolling:true,对于每个新请求(如果需要),使用指定的maxAge值来延长有效期。 否则,客户端会话将定期中断。 最后,saveUninitialized:false参数不会在redis中放置空会话。 这使您可以在应用程序级别上放置会话和passport.js的初始化,而不会因不必要的数据而阻塞redis。 在路由级别,只有在需要使用不同参数调用passport.initialize()方法时,才需要进行初始化。

如果不使用会话,则初始化将大大减少:

 app.use(passport.initialize()); 


接下来,您需要创建一个策略对象(因为passport.js在术语中调用了身份验证方法)。 每种策略都有其自己的配置功能。 唯一保持不变的是,回调函数被传递给策略构造函数,该结构构造了用户对象,可以作为request.user来访问以下中间件队列:

 const jwtStrategy = new JwtStrategy(params, (payload, done) => UserModel.findOne({where: {id: payload.userId}}) .then((user = null) => { done(null, user); }) .catch((error) => { done(error, null); }) ); 


我们必须非常清楚,如果不使用会话,则每次访问受保护的资源时都会调用此方法,并且数据库查询(如示例中)将严重影响应用程序的性能。

接下来,您需要给出命令以使用该策略。 每个策略都有一个默认名称。 但它也可以显式设置,从而允许使用一种具有不同参数和回调函数逻辑的策略:

 passport.use('jwt', jwtStrategy); passport.use('simple-jwt', simpleJwtStrategy); 


接下来,对于受保护的路由,您需要设置身份验证策略和重要的会话参数(默认为true):

 const authenticate = passport.authenticate('jwt', {session: false}); router.use('/hello', authenticate, (req, res) => { res.send('hello'); }); 


如果不使用会话,则必须通过所有受限制的访问路由来保护身份验证。 如果使用了会话,那么身份验证将发生一次,为此,将设置一条特殊的路由,例如login:

 const authenticate = passport.authenticate('local', {session: true}); router.post('/login', authenticate, (req, res) => { res.send({}) ; }); router.post('/logout', mustAuthenticated, (req, res) => { req.logOut(); res.send({}); }); 


通常,在使用会话时,在受保护的路由上,使用非常简洁的中间件(由于某些原因,它不包含在passport.js库中):

 function mustAuthenticated(req, res, next) { if (!req.isAuthenticated()) { return res.status(HTTPStatus.UNAUTHORIZED).send({}); } next(); } 


因此,最后一刻-将request.user对象从会话序列化和反序列化:

 passport.serializeUser((user, done) => { done(null, user.id); }); passport.deserializeUser((id, done) => { UserModel.findOne({where: {id}}).then((user) => { done(null, user); return null; }); }); 


我想再次强调,序列化和反序列化仅适用于设置了{session:true}属性的策略。 认证后,序列化将完全执行一次。 因此,更新会话中存储的数据将非常麻烦,因为只有用户ID(不会更改)会被保存。 反序列化将在对安全路由的每个请求上执行。 在这种情况下,数据库查询(如示例中)会严重影响应用程序性能。

备注。 如果您同时使用几种策略,则所有这些策略都可以使用相同的序列化/反序列化代码。 例如,要考虑执行身份验证的策略,可以在用户对象中包括策略属性。 用不同的值多次调用initialize()方法也没有意义。 仍将使用上次调用中的值重写它。

这可能是故事的结局。 因为 除了已经说过的以外,实际上不需要任何其他操作。 但是,应前端开发人员的要求,我必须在401响应中添加一个带有错误说明的对象(默认情况下,这是“未授权”行)。 事实证明,这不能简单地完成。 对于这种情况,您需要更深入地了解该库的核心,这并不是很好。 Passport.authenticate方法具有第三个可选参数:具有签名功能(错误,用户,信息)的回调函数。 一个小问题是响应对象或任何函数(例如done()/ next())都没有传递给此函数,因此您必须自己将其转换为中间件:

 route.post('/hello', authenticate('jwt', {session: false}), (req, res) => { res.send({}) ; }); function authenticate(strategy, options) { return function (req, res, next) { passport.authenticate(strategy, options, (error, user , info) => { if (error) { return next(error); } if (!user) { return next(new TranslatableError('unauthorised', HTTPStatus.UNAUTHORIZED)); } if (options.session) { return req.logIn(user, (err) => { if (err) { return next(err); } return next(); }); } req.user = user; next(); })(req, res, next); }; } 


有用的链接:

1) toon.io/了解密码验证流程
2) habr.com/post/201206
3) habr.com/company/ruvds/blog/335434
4) habr.com/post/262979
5) habr.com/company/Voximplant/blog/323160
6) habr.com/company/dataart/blog/262817
7) tools.ietf.org/html/draft-ietf-oauth-pop-architecture-08
8) oauth.net/articles/authentication

apapacy@gmail.com
一月4,2019

Source: https://habr.com/ru/post/zh-CN435106/


All Articles