@ fastify/ 护照
@fastify/passport 是一个端口 passport 为快速生态系统。 它允许您使用护照策略验证请求并保护快路!
状态
贝塔. @fastify/passport 仍是一个相对较新的项目。 可能与快递不相容 passport 部署和错误。 请报告任何问题,以便我们纠正!
安装
npm i @fastify/passport
Google OAuth2 视频教程
社区创造了这个快速的介绍 @fastify/passport数字 :

示例
import fastifyPassport from '@fastify/passport'
import fastifySecureSession from '@fastify/secure-session'
const server = fastify()
// set up secure sessions for @fastify/passport to store data in
server.register(fastifySecureSession, { key: fs.readFileSync(path.join(__dirname, 'secret-key')) })
// initialize @fastify/passport and connect it to the secure-session storage. Note: both of these plugins are mandatory.
server.register(fastifyPassport.initialize())
server.register(fastifyPassport.secureSession())
// register an example strategy for fastifyPassport to authenticate users using
fastifyPassport.use('test', new SomePassportStrategy()) // you'd probably use some passport strategy from npm here
// log users in with the strategy named "test": on success the user is stored in the session and redirected to /
server.post(
'/login',
{ preValidation: fastifyPassport.authenticate('test', { successRedirect: '/', authInfo: false }) },
() => {}
)
// require a login for a route: the session hook has already populated request.user for logged-in requests
server.get(
'/',
{
preValidation: async (request, reply) => {
if (!request.isAuthenticated()) return reply.redirect('/login')
}
},
async () => 'hello world!'
)
server.listen()
或者, @fastify/session 也支持并工作在会话存储的框外.
举个例子:
import { Authenticator } from '@fastify/passport'
import fastifyCookie from '@fastify/cookie'
import fastifySession from '@fastify/session'
const server = fastify()
// setup an Authenticator instance which uses @fastify/session
const fastifyPassport = new Authenticator()
server.register(fastifyCookie)
server.register(fastifySession, { secret: 'secret with minimum length of 32 characters' })
// initialize @fastify/passport and connect it to the secure-session storage. Note: both of these plugins are mandatory.
server.register(fastifyPassport.initialize())
server.register(fastifyPassport.secureSession())
// register an example strategy for fastifyPassport to authenticate users using
fastifyPassport.use('test', new SomePassportStrategy()) // you'd probably use some passport strategy from npm here
保护路线
authenticate() 运行一个策略。 它不检查请求是否已经存在会话, 因此它是路径的右钩 执行 登录(登录路线和OAuth召回路线),错误的路线 需求 一个.
一次 secureSession() 已注册,每个包含登录会话的请求已经 request.user 在任何路线级的钩子运行之前,有人为你服务。 警卫保护路线在那,与 request.isAuthenticated()数字 :
const requireLogin = async (request, reply) => {
if (!request.isAuthenticated()) {
request.session.set('returnTo', request.url) // optional, see successReturnToOrRedirect
return reply.redirect('/auth/login')
}
}
// performs the login
server.get('/auth/login', { preValidation: fastifyPassport.authenticate('google') }, () => {})
server.get(
'/auth/callback',
{ preValidation: fastifyPassport.authenticate('google', { successRedirect: '/' }) },
() => {}
)
// requires a login
server.get('/', { preValidation: requireLogin }, async (request) => request.user)
采用重定向战略(任何OAuth/OIDC战略): passport-microsoft, (中文(简体) ), passport-google-oauth20, (中文(简体) ), passport-github2,.)在被保护的路由上产生无限重定向循环而不是出错,因为策略在请求中没有证书,因此重定向到身份提供者,提供者承认自己的会话,并立即重定向回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回调回
// DON'T: '/' bounces to the IdP and back forever, even for a logged-in user
server.get('/', { preValidation: fastifyPassport.authenticate('google') }, async (request) => request.user)
还注意到 authenticate('session') 不是警卫 内在会话战略 通过 当会话没有用户而非失败时 failureRedirect 永远不要起火, 和操作员运行 request.user 未设.
登录的会话清理
出于安全考虑,会话在登录后被清理. 您可以通过 :
- 包含
keepSessionInfo执行护照时的真实选项.authenticate电话; - 包含
keepSessionInfo执行请求时的真实选项.login电话; - 使用
clearSessionOnLogin (default: true)财务报告和审定财务报表clearSessionIgnoreFields (default: ['passport', 'session'])。 。 。 .
差异 @fastify/secure-session 财务报告和审定财务报表 @fastify/session
@fastify/secure-session 财务报告和审定财务报表 @fastify/session 是两个可加密/解密会话的会话插件。 主要的区别在于 @fastify/secure-session 使用无国籍方法,将整个会话存储在加密的饼干中,而 @fastify/session 在会话中使用状态化方法并将其存储在会话商店中.
会话序列化
在一个典型的网络应用程序中,用于认证用户的证书只有在用户登录进入时和之后才会被传送一次,因为有些数据存储在其会话中,才被视为登录. @fastify/passport 通过使用 @fastify/secure-session,并将用户对象序列化/去序化,使之与饼干引用的会话相接. @fastify/passport 无法在会话中存储丰富的对象类, 只保存 JSON 对象, 因此, 如果您想要从数据库中获取用户对象, 您必须注册序列器/ 删除器对, 并且只存储会话中的用户 ID .
// register a serializer that stores the user object's id in the session ...
fastifyPassport.registerUserSerializer(async (user, request) => user.id);
// ... and then a deserializer that will fetch that user from the database when a request with an id in the session arrives
fastifyPassport.registerUserDeserializer(async (id, request) => {
return await User.findById(id);
});
API 密码
初始化()
钩子 必须添加设置 @fastify/passport 实验的钩子.
安全会议
钩子 必须添加设置 @fastify/passport与 @fastify/secure-session 在会话中存储认证.
认证( 战略: 字符串 QQ 策略 QQ (字符串 QQ 策略) ) [], 选项: 认证选项, 回调
返回认证请求的钩子,换句话说,验证用户,然后将其签入. authenticate 用作 preValidation 钩在特定的路线上像 /login。 。 。 .
将给定的战略(或策略)应用到收到的请求中,以认证请求. 战略通常在时间之前使用 .use,然后转至 .authenticate 以名为相. 如果认证成功,用户将在 request.user 并默认设立会话。 如果认证失败, 将发出未经授权的响应 .
战略或一系列战略也可作为实例来通过。 当您只打算对一个用户使用一次的临时策略, 并且不想注册到可用策略的全球列表时, 这样做是有用的 .
选项 :
session在会话中保存登录状态,默认为 真实successRedirect成功登录后, 重定向到指定的 URLsuccessReturnToOrRedirect成功登录后, 重定向到会话中存储的 URLreturnTo如果有的话,则使用给定的URL。 注意默认登录时会话已被清除, 因此returnTo必须保留:添加'returnTo'页:1Authenticator是因为clearSessionIgnoreFields,或通过keepSessionInfo: true此处(参见 登录的会话清理(中文(简体) )successMessage将成功消息存储到 req. session. messages, 或用作覆盖的字符串 成功的信息.successFlash用于闪存成功消息或用作闪存的字符串的真伪 成功信息(取代战略本身的任何信息).failureRedirect登录失败后, 重定向到给定的 URLfailureMessage将失败消息存储到 req. session. messages, 或用作覆盖的字符串 失败信息.failureFlash用于闪存失败消息或用作闪存的字符串的真伪 失败信息(超过战略本身的任何信息).assignProperty将验证回调对象指定为给定属性authInfo通过info通过已注册的认证信息转换器返回策略的验证回调,并将结果存储在request.authInfo,默认为 真实设置为 虚假 跳过此选项failWithError在失败的登录时, 丢出一个AuthenticationError而不是发送失败响应, 因此它会到达您的快速错误处理器state将任何提供的状态传递到策略中( 例如 Google Oauth)keepSessionInfo认证后保存现有会话属性的真伪
可选 callback 能够提供允许应用程序推翻处理认证尝试的默认方式。 召回的签名如下:
(request, reply, err | null, user | false, info?, (status | statuses)?) => Promise<void>
地点 request 财务报告和审定财务报表 reply 将设置为原始 FastifyRequest 财务报告和审定财务报表 FastifyReply 对象,以及 err 将设定为 null 在成功或成功的情况下 Error 对象,如果失败。 若为 err 没有 null 接下来 user, (中文(简体) ), info,以及 status 对象将是 undefined编辑 user 对象将在认证尝试成功时设定给认证的用户,或 false 否则.
可选 info 参数将会被传递, 包含由策略的验证回调提供的更多细节 - 这可以是关于成功认证的信息, 也可以是失败认证的质疑信息 .
可选 status 或者说 statuses 当认证失败时会通过参数 - 这可以是远程认证失败或类似的 HTTP 响应代码 .
fastify.get(
'/',
{ preValidation: fastifyPassport.authenticate('test', { authInfo: false }) },
async (request, reply, err, user, info, status) => {
if (err !== null) {
console.warn(err)
} else if (user) {
console.log(`Hello ${user.name}!`)
}
}
)
实例:
// create a request handler that uses the Facebook strategy
fastifyPassport.use(new FacebookStrategy('facebook', {
// options for the Facebook strategy, see https://www.npmjs.com/package/passport-facebook
})))
fastifyPassport.authenticate('facebook');
// create a request handler to test against the strategy named local, and automatically redirect when it succeeds or fails
fastifyPassport.authenticate('local', { successRedirect: '/', failureRedirect: '/login' });
// create a request handler that won't use any user information stored in the secure session
fastifyPassport.authenticate('basic', { session: false });
注意如果提供回调,则成为应用程序登录用户的责任,建立会话,并进行所期望的操作.
多重战略
@fastify/passport 支持以策略列表进行认证,并按顺序逐一进行,直到一个通过。 传出一系列策略名称到 authenticate 为此:
// somewhere before several strategies are registered
fastifyPassport.use('bearer', new BearerTokenStrategy())
fastifyPassport.use('basic', new BasicAuthStrategy())
fastifyPassport.use('google', new FancyGoogleStrategy())
// and then an `authenticate` call can test incoming requests against multiple strategies
fastify.get(
'/',
{ preValidation: fastifyPassport.authenticate(['bearer', 'basic', 'google'], { authInfo: false }) },
async (request, reply, err, user, info, status) => {
if (err !== null) {
console.warn(err)
} else if (user) {
console.log(`Hello ${user.name}!`)
}
}
)
请注意,重定向启动认证流的多种策略,如来自主要平台的OAuth2策略,不应在同一平台一起使用 authenticate 电话 这是因为 @fastify/passport 将按顺序运行策略,而第一个重定向策略将这样做,防止用户使用其他策略. 要设置多个 OAuth2 策略, 需要添加多个路线, 每个路线都使用不同的策略 authenticate ,然后引导用户选择正确的策略.
多个策略也可以作为实例通过,如果您只打算用于路由处理器或请求.
// use an `authenticate` call can test incoming requests against multiple strategies without registering them for use elsewhere
fastify.get(
'/',
{
preValidation: fastifyPassport.authenticate([new BearerTokenStrategy(), new BasicAuthStrategy()], {
authInfo: false,
}),
},
async (request, reply, err, user, info, status) => {
if (err !== null) {
console.warn(err)
} else if (user) {
console.log(`Hello ${user.name}!`)
}
}
)
授权( 策略: 字符串 QQ 策略 QQ (字符串 QQ 策略 ) [], 选项: 认证选项 = QQ, 回调
返回将授权使用给定的第三方账户的钩子 strategy,可选 options。旨在用作一种 preValidation 钩上任何路线. .authorize 有与 .authenticate,但有一个关键区别: 它不会修改用户细节中记录的. 相反,如果授权成功,该战略的核查回调结果将分配给 request.account。现有的登录会话和 request.user 将不受影响.
在将第三方账户与目前认证的用户的本地账户连接时,这一功能特别有用.
实例:
fastifyPassport.authorize('twitter-authz', { failureRedirect: '/account' })
.authorize 允许通过通过一系列战略名称来使用多种战略,并允许通过通过将实例作为战略来使用已经即时的战略实例,或一系列实例.
使用( 名称? : 字符串, 策略: 策略)
使用给定的 strategy 可选 name,压倒策略的默认名称.
实例:
fastifyPassport.use(new TwitterStrategy(...));
fastifyPassport.use('api', new http.Strategy(...));
未使用( 名称: 字符串)
取消对 strategy 给定 name。 。 。 .
在典型的应用程序中,必要的认证策略是静态的,配置一次,总是可用的. 因此,往往没有必要援引这一职能.
然而,在某些情况下,应用程序可能需要动态配置和去配置认证策略. 这个 use()编号 :unuse() 组合满足了这些设想.
示例
fastifyPassport.unuse('legacy-api')
注册用户序列器( 序列器 ): (用户, 请求) QQ 承诺)
注册一个同步用户序列器功能,用于从您的应用程序中取出一个高级用户对象并将其序列化存储到会话中. @fastify/passport 无法在会话中存储丰富的对象类, 只保存 JSON 对象, 因此, 如果您想要从数据库中获取用户对象, 您必须注册序列器/ 删除器对, 并且只存储会话中的用户 ID .
// register a serializer that stores the user object's id in the session ...
fastifyPassport.registerUserSerializer(async (user, request) => user.id)
用户定义器( 定义器 :( 序列化用户, 请求) QQ 承诺)
注册一个 async 用户 deserializer 函数,用于从会话中取出一个低级序列化用户对象(通常只是用户ID),并将其从存储到请求上下文中去除. @fastify/passport 无法在会话中存储丰富的对象类, 只保存 JSON 对象, 因此, 如果您想要从数据库中获取用户对象, 您必须注册序列器/ 删除器对, 并且只存储会话中的用户 ID .
fastifyPassport.registerUserDeserializer(async (id, request) => {
return await User.findById(id);
});
断层器可以抛出字符串 "pass" 如果它们不适用于本届会议,则应审判下一个破坏者。 如果您正在使用此选项, 则此选项将有所帮助 @fastify/passport 以存储两种不同的用户对象。 一个例子是:
// register a deserializer for database users
fastifyPassport.registerUserDeserializer(async (id, request) => {
if (id.startsWith("db-")) {
return await User.findById(id);
} else {
throw "pass"
}
});
// register a deserializer for redis users
fastifyPassport.registerUserDeserializer(async (id, request) => {
if (id.startsWith("redis-")) {
return await redis.get(id);
} else {
throw "pass"
}
});
会话可以指定自此被从存储用于应用程序的数据存储所删除的序列化用户. 在这种情况下,去序列化往往失败,因为无法为给定的ID找到用户行. 根据应用程序的不同,这可能是一个出错条件,或者如果用户在登录时被从数据库中删除,预计会是这样. @fastify/passport本案中的行为可以形容. 如果解析器返回未定义, 错误会被丢出; 如果解析器返回, 会话会被记录出去 null 或者说 false. 这符合原作的行为 passport 模块.
因此,一个破坏者可以返回一些东西:
- 如果一个去序列器返回一个对象,则该对象被假定为成功去序列化用户
- 如果一个破坏者返回
undefined, (中文(简体) ),@fastify/passport将它解释为错误地缺失的用户,并扔出一个错误,因为用户无法被解析. - 如果一个破坏者返回
null或者说false, (中文(简体) ),@fastify/passport将它解释为缺失但预期的用户,并将会话重排以登录用户退出 - 如果断层器抛出字符串
"pass", (中文(简体) ),@fastify/passport将尝试下一个去序列化器,如果它存在的话,或者由于用户无法去序列化而丢出一个错误.
请求# is 认证( )
测试请求是否包含登录会话, 即是否 request.user 居住人口 secureSession() 或由先前 authenticate() 电话 这就是应该保护的路线 保护路线。 。 。 .
server.get('/', {
preValidation: async (request, reply) => {
if (!request.isAuthenticated()) return reply.redirect('/auth/login')
}
}, async (request) => request.user)
请求# 是未经认证的 ()
测试请求是否未经认证.
使用类型脚本
@fastify/passport 以 TypeScript 写成,所以它包括了它所有API的类型定义. 也可以强烈地输入 FastifyRequest.user 属性使用 TypeScript 声明合并。 你必须重新宣布 PassportUser 接口 fastify 模块,用于在认证时添加策略指定的属性 :
declare module 'fastify' {
interface PassportUser {
id: string
}
}
或者,如果您已经拥有从所有策略中返回的对象的类型,您可以制作 PassportUser 扩展它:
import { User } from './my/types'
declare module 'fastify' {
interface PassportUser extends User {}
}
使用多个实例
@fastify/passport 支持在不同插件封装上下文中多次注册。 这对执行两个独立的认证堆栈很有用. 例如,你可能有一套战略来认证你的应用程序的用户,还有一套其他战略来认证进入某一行政区域的应用程序的工作人员。 用户可能存储于 request.user,和管理员 request.admin,而一个伐木 不应该影响另一个。 必须登记每个实例: @fastify/passport 在不同的快捷插件上下文中使装饰器 @fastify/passport 喜欢 request.logIn 财务报告和审定财务报表 request.logOut 别撞了.
要登记@ fastify/ passport 不止一次, 您必须即刻获取更多不同的副本 keys 财务报告和审定财务报表 userProperty因此,它们不会在装饰您固定实例或存储会话时碰撞.
import { Authenticator } from '@fastify/passport'
const server = fastify()
// users: their own Authenticator, hooks and routes, inside their own plugin context
server.register(async (instance) => {
const userPassport = new Authenticator({ key: 'users', userProperty: 'user' })
userPassport.use('some-strategy', new CoolOAuthStrategy('some-strategy'))
instance.register(userPassport.initialize())
instance.register(userPassport.secureSession())
instance.get('/login', { preValidation: userPassport.authenticate('some-strategy') }, () => {})
instance.get(
'/login/callback',
{ preValidation: userPassport.authenticate('some-strategy', { successRedirect: '/' }) },
() => {}
)
instance.get(
'/',
{
preValidation: async (request, reply) => {
if (!request.isAuthenticated()) return reply.redirect('/login')
}
},
async (request) => `hello ${JSON.stringify(request.user)}!`
)
})
// administrators: the same, in a separate context so the decorators don't collide
server.register(async (instance) => {
const adminPassport = new Authenticator({ key: 'admin', userProperty: 'admin' })
adminPassport.use('admin-google', new GoogleOAuth2Strategy('admin-google'))
instance.register(adminPassport.initialize())
instance.register(adminPassport.secureSession())
instance.get('/admin/login', { preValidation: adminPassport.authenticate('admin-google') }, () => {})
instance.get(
'/admin/login/callback',
{ preValidation: adminPassport.authenticate('admin-google', { successRedirect: '/admin' }) },
() => {}
)
instance.get(
'/admin',
{
preValidation: async (request, reply) => {
if (!request.isAuthenticated()) return reply.redirect('/admin/login')
}
},
async (request) => `hello administrator ${JSON.stringify(request.admin)}!`
)
})
说明: 每个 Authenticator 实例初始化插件和会话插件必须单独注册.
必须指出的是,使用多种方法 @fastify/passport 如果想要使用多个策略登录同一类型的用户,则不需要实例. @fastify/passport 将数组传递到任意 .authenticate 电话.
与护照的区别
@fastify/passport 是一个经过修改的护照版本,试图尽可能兼容,但是一个经过修改的版本,有一些不相容之处. 坚持护照策略的护照策略API应该很好,但为了更好地与Fastify结合并坚持Fastify的性能主题而制作的其他API有一些不同.
差异:
serializeUser重新命名为registerUserSerializer并总是使用签名的同步函数(user: User, request: FastifyRequest) => Promise<SerializedUser>deserializeUser重新命名为registerUserDeserializer并总是使用签名的同步函数(serialized: SerializedUser, request: FastifyRequest) => Promise<User>transformAuthInfo重新命名为registerAuthInfoTransformer并总是使用签名的同步函数(info: any, request: FastifyRequest) => Promise<any>.authenticate财务报告和审定财务报表.authorize除战略名称外,接受战略实例。 这使得可以使用一次性战略实例(例如测试给定的用户资格),而不将它们添加到全球已登记战略清单中.