拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Midway 认证鉴权实战:@midwayjs/passport 组件用法、版本演进与源码实现全解析

Midway 认证鉴权实战:@midwayjs/passport 组件用法、版本演进与源码实现全解析 后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载身份验证是绝大多数 Web 应用无法绕开的核心能力。Midway 将 Node.js 生态中最流行的 Passport 库封装为midwayjs/passport组件通过「策略Strategy 中间件Middleware」的可插拔模式让开发者可以用几乎一致的代码完成本地账号密码认证、JWT 无状态认证、GitHub 等第三方 OAuth 登录。本篇文章以 packages/passport/CHANGELOG.md 记录的版本演进为脉络结合 packages/passport/README.md 的完整实操示例与组件源码实现系统梳理该组件的能力边界、使用方法与底层原理。读完你将能独立在 Midway 应用中接入任意 Passport 策略并理解中间件工厂、认证链等核心机制是如何工作的。一、组件定位为什么 Midway 要封装 PassportPassport 的核心设计哲学是通过称为策略Strategy的可扩展插件来认证请求。它不挂载路由、不假设任何特定的数据库这最大限度地提高了灵活性允许开发者在应用层面做出认证决策。Midway 在此基础上做了面向 IoC 容器的封装将策略的注册、中间件的生成、请求对象的适配全部纳入容器管理。从源码结构看组件由以下几部分构成文件职责src/decorator/strategy.ts提供CustomStrategy()装饰器将策略类注册为单例并交给容器托管src/passport/passport.service.ts提供PassportStrategy、PassportMiddleware两个工厂函数是组件的核心src/passport/authenticator.tsPassportAuthenticator单例维护策略注册表与序列化/反序列化器执行认证链src/passport/request.ts将 Koa 的ctx适配为 Express 风格req对象的代理实现src/config/config.default.ts组件默认配置src/interface.tsAuthenticateOptions等类型定义midwayjs/passport支持 Express、Koa 与 Faas 三种运行模式这也是 CHANGELOG 中多次出现「express/koa/faas」相关修复的原因。二、从 CHANGELOG 看组件能力演进packages/passport/CHANGELOG.md完整记录了组件自 3.0.0-beta 以来每一版本的变更提炼关键节点可以清晰看到组件能力的成长路径版本时间关键变更能力解读3.0.0-beta.82021-12-08支持 passport 与 jwt新增presetProperty修复 express 路由级中间件与缺失 proxy 文件组件具备基本的 JWT 接入能力并开始兼容 Express 路由中间件3.0.0-beta.102021-12-20默认支持 koa/express/faas 的 session 与 bodyparser三种运行模式统一获得会话能力3.0.0-beta.122021-12-28支持抛出带 status 的错误认证失败可携带 HTTP 状态码3.0.0-beta.172022-01-18注册时机迁移到 onServerReady允许用户在认证后提供响应中间件生命周期与框架更贴合认证后的响应可自定义3.4.42022-07-25修复部分策略 verify 回调参数长度问题对应源码中Object.defineProperty(cb, length, ...)的处理3.4.132022-08-24修复策略中this丢失问题对应源码中对serializeUser等方法的bind(this)处理3.6.02022-10-10新增 guard组件进一步贴近 Midway 的守卫鉴权体系其余大量版本为纯版本号提升Version bump说明该组件依赖 lerna 工作区整体发版。需要说明的是CHANGELOG 记录的是 3.x 时代的演进而当前仓库 package.json 中的版本号已迭代到4.2.3功能在此基础上继续演进。从源码结构看这一演进脉络最终沉淀为三件事策略如何注册、中间件如何生成、认证结果如何处理。下面逐层展开。三、快速开始安装与启用安装组件及相关依赖$ npm i midwayjs/passport passport --save $ npm i types/passport --save-dev如有需要同时开启对应框架的 bodyparser 与 session例如express-session、Koa 的 session 中间件这是登录会话与解析请求体的前提。在configuration.ts中启用组件JWT 场景还需同时引入midwayjs/jwt// configuration.ts import { join } from path; import * as jwt from midwayjs/jwt; import { ILifeCycle } from midwayjs/core; import { Configuration } from midwayjs/core; import * as passport from midwayjs/passport; Configuration({ imports: [ jwt, passport, ], importConfigs: [join(__dirname, ./config)], conflictCheck: true, }) export class ContainerLifeCycle implements ILifeCycle {}组件加载时configuration.ts 会在onReady阶段获取PassportAuthenticator单例完成策略注册表的初始化若开启了 session还会自动注册内置的SessionStrategy见 authenticator.ts 的init方法。四、实战一本地账号密码认证通过CustomStrategy()装饰器与派生PassportStrategy工厂即可自定义一个本地认证策略。validate钩子用于获取有效负载并校验此函数必须有返回值其参数并不固定需要参考对应 Strategy 的文档或通过展开符打印查看。// local-strategy.ts import { CustomStrategy, PassportStrategy } from midwayjs/passport; import { Repository } from typeorm; import { InjectEntityModel } from midwayjs/orm; import { UserEntity } from ./user; import * as bcrypt from bcrypt; CustomStrategy() export class LocalStrategy extends PassportStrategy(Strategy) { InjectEntityModel(UserEntity) userModel: RepositoryUserEntity; // 策略的验证 async validate(username, password) { const user await this.userModel.findOne({ username }); if (!(await bcrypt.compare(password, user.password))) { throw new Error(error password username); } return { username, password, }; } // 当前策略的参数 getStrategyOptions(): any { return {}; } }说明bcrypt.compare的判定逻辑已按常规语义整理原文档示例存在条件写反的笔误实际接入时请以你自己的密码校验实现为准。再派生一个PassportMiddleware中间件并通过getAuthenticateOptions设置认证选项// local-middleware.ts import { Inject, Provide } from midwayjs/core; import { PassportMiddleware } from midwayjs/passport; import { Context } from midwayjs/express; Provide() export class LocalPassportMiddleware extends PassportMiddleware(LocalStrategy) { // 设置 AuthenticateOptions getAuthenticateOptions(): Promisepassport.AuthenticateOptions | passport.AuthenticateOptions { return { failureRedirect: /login, presetProperty: user }; } }在控制器中挂载该中间件// controller.ts import { Provide, Post, Inject, Controller } from midwayjs/core; Provide() Controller(/) export class LocalController { Post(/passport/local, { middleware: [LocalPassportMiddleware] }) async localPassport() { console.log(local user: , this.ctx.req.user); return this.ctx.req.user; } }使用 curl 模拟一次请求curl -X POST http://localhost:7001/passport/local -d {username: demo, password: 1234} -H Content-Type: application/json # 结果 {username: demo, password: 1234}五、实战二JWT 无状态认证首先安装npm i midwayjs/jwt然后在config/config.ts中配置export const jwt { secret: xxxxxxxxxxxxxx, // 或 fs.readFileSync(xxxxx.key) expiresIn: 2d, // 过期时间格式参考 vercel/ms 库 };⚠️ JWT 默认未加密不要把敏感信息存放在 payload 中。编写 JWT 策略。passport-jwt的Strategy需要secretOrKey与jwtFromRequest两个核心选项这里使用ExtractJwt.fromAuthHeaderAsBearerToken()从Authorization: Bearer xxx请求头中提取令牌// strategy/jwt-strategy.ts import { CustomStrategy, PassportStrategy } from midwayjs/passport; import { Strategy, ExtractJwt } from passport-jwt; CustomStrategy() export class JwtStrategy extends PassportStrategy( Strategy, jwt // 给策略命名多策略并存时用于区分 ) { Config(jwt) jwtConfig; async validate(payload) { return payload; } getStrategyOptions(): any { return { secretOrKey: this.jwtConfig.secret, jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), }; } }// jwt-middleware.ts import { Provide } from midwayjs/core; import { PassportMiddleware } from midwayjs/passport; import { JwtStrategy } from ./strategy/jwt-strategy; Provide() export class JwtPassportMiddleware extends PassportMiddleware(JwtStrategy) { getAuthenticateOptions(): Promisepassport.AuthenticateOptions | passport.AuthenticateOptions { return {}; } }控制器同时提供签发与校验两个接口import { Provide, Post, Inject } from midwayjs/core; import { Controller, Post } from midwayjs/core; import { Jwt } from midwayjs/jwt; Provide() Controller(/) export class JwtController { Inject() jwt: Jwt; Inject() ctx: any; Post(/passport/jwt, { middleware: [JwtPassportMiddleware] }) async jwtPassport() { console.log(jwt user: , this.ctx.req.user); return this.ctx.req.user; } Post(/jwt) async genJwt() { return { t: await this.jwt.sign({ msg: Hello Midway }), }; } }curl 模拟完整流程# 1. 签发令牌 curl -X POST http://127.0.0.1:7001/jwt # 结果 {t: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx} # 2. 携带令牌访问受保护接口 curl http://127.0.0.1:7001/passport/jwt -H Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # 结果 {msg: Hello Midway,iat: 1635468727,exp: 1635468827}六、实战三自定义第三方 OAuth 策略GitHub 为例midwayjs/passport支持任意 Passport 生态的 [策略包]。这里以passport-github为例npm i passport-github// github-strategy.ts import { CustomStrategy, PassportStrategy } from midwayjs/passport; import { Strategy, StrategyOptions } from passport-github; const GITHUB_CLIENT_ID xxxxxx, GITHUB_CLIENT_SECRET xxxxxxxx; CustomStrategy() export class GithubStrategy extends PassportStrategy(Strategy, github) { async validate(...payload) { return payload; } getStrategyOptions() { return { clientID: GITHUB_CLIENT_ID, clientSecret: GITHUB_CLIENT_SECRET, callbackURL: https://127.0.0.1:7001/auth/github/cb }; } }// github-middleware.ts import { PassportMiddleware } from midwayjs/passport; Provide() export class GithubPassportMiddleware extends PassportMiddleware { }注意这里中间件派生时没有传入策略类因为PassportMiddleware在认证时会遍历已注册到PassportAuthenticator的全部策略见下文认证链机制OAuth 场景需要发起跳转后由回调地址再次触发认证。// controller.ts import { Provide, Get, Inject } from midwayjs/core; Provide() Controller(/oauth) export class AuthController { Inject() ctx: any; Get(/github, { middleware: [GithubPassportMiddleware] }) async githubOAuth() {} Get(/github/cb, { middleware: [GithubPassportMiddleware] }) async githubOAuthCallback() { return this.ctx.req.user; } }首个路由将用户 302 重定向到 GitHub 授权页对应策略的redirect动作回调路由完成认证并返回用户信息。七、核心机制源码级解析7.1 PassportStrategy 工厂策略如何被实例化PassportStrategy(Strategy, name?)返回一个继承了AbstractStrategyWrapper的抽象类passport.service.ts其内部在Init()阶段完成四件事包装validate为 Passport 风格回调将validate(...)的返回值通过Promise.resolve处理数组结果会展开传给done异常则交给done(err, null)修正回调参数长度通过Object.defineProperty(cb, length, ...)将回调的length设为validate.length 1这正是 CHANGELOG 3.4.4 中「verify callback arguments length」修复的实现方式——部分策略会根据回调函数参数个数调整调用方式实例化底层策略以new Strategy(this.getStrategyOptions(), cb)创建 Passport 原生策略实例getStrategyOptions()的返回值即策略构造参数注册到认证器passport.use(name, strategy)注册策略若类中定义了serializeUser、deserializeUser、transformAuthInfo则分别以bind(this)方式注册对应 CHANGELOG 3.4.13 中「strategy this missing」修复避免方法内this指向丢失。7.2 PassportMiddleware 工厂中间件如何工作PassportMiddleware(strategy | strategy[])返回继承AbstractPassportMiddleware的中间件类passport.service.ts核心流程如下入口resolve()直接返回this.authenticate(this.getAuthenticateOptions())子类只需实现getAuthenticateOptions即可双模式适配通过PassportAuthenticator.isExpressMode()判断运行框架检测容器是否注册了express命名空间。Express 模式直接改造原生reqKoa 模式则用 request.ts 的create(ctx, userProperty)生成 Express 风格的req代理并额外为ctx挂载ctx.login/logIn、ctx.logout/logOut、ctx.isAuthenticated、ctx.isUnauthenticated别名选项合并{ ...this.passportConfig, ...options }将组件默认配置与中间件getAuthenticateOptions()返回值合并策略实例化时机认证前通过app.getApplicationContext().getAsync(strategySingle)从容器获取策略单例确保策略完成注册对应 CHANGELOG 3.0.0-beta.17 中「move register to onServerReady」的调整认证结果分发调用passport.authenticate([], authOptions)后根据返回的successResult/redirectResult/failResult分别进入onceSucceed、重定向、allFailed三个分支onceSucceedL254-L317处理successMessage写入req.session.messages、assignProperty将用户挂载到请求指定属性、req.logIn登录会话、authInfo变换、successRedirect/successReturnToOrRedirect跳转allFailedL319-L387处理failureMessage、failureRedirect否则默认以401响应并设置WWW-Authenticate头——对应 CHANGELOG 3.0.0-beta.12 中「支持抛出带 status 的错误」最终抛出httpError.UnauthorizedError()认证过程中抛出的非MidwayHttpError异常会被包装为 500 错误对应 CHANGELOG 3.4.3 中「throw error when authenticate」的修复。attachRequestMethodL389-L454为请求对象补齐req.login/logIn、req.logout/logOut、req.isAuthenticated、req.isUnauthenticated方法其中登录与会话写入均委托给PassportAuthenticator。7.3 PassportAuthenticator认证链与会话管理PassportAuthenticatorauthenticator.ts是组件的单例核心策略注册表内部strategies: Map维护全部策略use(name, strategy)/unuse(name)负责增删认证链authenticate()L110-L244顺序执行所有注册策略为每个策略实例注入success/fail/redirect/pass/error五个动作闭包——任一策略调用success或redirect即短路结束fail则累积失败信息继续尝试下一个策略error直接中断并抛出异常序列化体系serializeUser/deserializeUser/transformAuthInfo均实现为可串联执行的栈结构任一注册函数返回结果即终止否则依次传递可返回字符串pass主动跳过会话登录登出logInToSession将序列化后的用户写入req.session[sessionUserProperty].userlogOutFromSession清除用户并regenerate会话防止会话固定攻击。两者都会在缺少 session 时报错提示——这正是 README「开启对应框架 session」这一准备步骤的原因。7.4 关于 pause.tssrc/passport/pause.ts 是对 Passport 经典实现中流暂停机制的保留用于处理pauseStream场景下的请求流控制配套的 test/pause.test.ts 单独覆盖了该模块的行为。八、AuthenticateOptions 参数速查getAuthenticateOptions()返回的认证选项定义于 interface.ts关键参数及语义如下参数类型作用sessionboolean登录成功后是否将状态写入会话默认由组件配置控制默认trueassignPropertystring认证成功后把 user 挂载到请求对象的指定属性onceSucceed中req[assignProperty] useruserPropertystring用户属性名默认user即最终可通过req.user/ctx.state.user访问successRedirectstring登录成功后跳转的 URLsuccessReturnToOrRedirectstring优先跳转req.session.returnTo否则跳转给定 URLsuccessMessageboolean \| string为true时把info.message写入req.session.messages为字符串时作为覆盖文案failureRedirectstring认证失败后的跳转 URLfailureMessageboolean \| string失败时写入req.session.messages的消息authInfoboolean是否将策略返回的info经变换后挂载到req.authInfo默认不关闭scopestring \| string[]OAuth 场景下向第三方申请的作用域statestringOAuth 场景下的状态参数pauseStreamboolean认证期间是否暂停请求流对应 pause 模块passReqToCallbackboolean是否把req作为首个参数传给 verify 回调promptstring认证提示信息提示README 示例中使用了presetProperty字段3.0.0-beta.8 引入而当前仓库的接口层与中间件实现中以assignProperty承担相同语义。不同版本字段命名存在差异落地时请以当前安装版本的类型声明为准。九、默认配置说明组件默认配置位于 config.default.tsexport const passport { session: true, // 默认启用会话登录态 assignProperty: true, // 认证成功后把 user 赋到请求对象 sessionUserProperty: user, // 会话中存放用户信息的属性名 userProperty: user, // 请求对象上用户属性名req.user / ctx.state.user };这些默认值会被中间件getAuthenticateOptions()的返回值覆盖合并顺序{...passportConfig, ...options}因此组件级的passport配置可以在config/config.default.ts中统一调整例如全局修改userProperty。十、测试与验证组件在 test/ 下提供测试用例index.test.ts覆盖策略注册、认证链与 Express/Koa 双模式的集成行为pause.test.ts覆盖请求流暂停逻辑。从 package.json 的 devDependencies 可以看到官方测试环境同时引入了passport-local、passport-jwt、passport-http-bearer、passport-openidconnect与express-session覆盖了本地、JWT、Bearer Token、OIDC 等多种策略形态可作为自定义策略接入时的参照。小结midwayjs/passport以「策略工厂 中间件工厂 认证器单例」三层结构把 Passport 生态无缝嫁接到 Midway 的 IoC 容器中。从 CHANGELOG 的演进可以看到会话支持、presetProperty、带状态错误抛出、guard、双框架兼容等能力是逐步补齐的而源码中的每一处细节回调长度修正、this绑定、Koa/Express 双模式适配、认证链短路都有对应的变更记录与之呼应。对于需要认证鉴权能力的 Midway 应用这份组件即是「策略即插即用、中间件一行挂载」的完整答案。赞分享后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载相关推荐Blockly blockly/field-date 插件全解析版本演进、安装用法与源码实现Blockly blockly/field date 插件全解析版本演进、安装用法与源码实现 blockly/field date 是 Blockly 官前端低代码UI组件Midway 接入 MikroORM v7midwayjs/mikro7 独立组件实战指南Midway 接入 MikroORM v7 midwayjs/mikro7 独立组件实战指南 midwayjs/mikro7 是 Midway 为 Mik后端微服务云原生OpenWhispr双窗口架构解析听写悬浮层与控制面板的设计哲学OpenWhispr双窗口架构解析听写悬浮层与控制面板的设计哲学 OpenWhispr 是一款隐私优先的跨平台语音转文字听写应用支持本地模型Nvidia后端微服务云原生上一篇Buefy开发效率提升开发速度的10个技巧下一篇Californium多传输协议支持UDP、TCP、TLS和WebSockets终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表