LoopBack 4 认证 Action 深度解析:从序列伪代码到中间件自动化认证
后端微服务【免费下载链接】loopback-nextLoopBack makes it easy to build modern API applications that require complex integrations.项目地址https://gitcode.com/gh_mirrors/lo/loopback-next点击查看免费下载loopback/authentication是 LoopBack 4 的认证组件其核心逻辑集中于一个被称为authentication action认证动作的模块它负责解析端点上的认证元数据、按名称查找认证策略、调用策略的authenticate()校验请求并把校验出的用户画像UserProfile绑定到请求上下文供后续处理器注入。本文以 authentication-action.md 为主体结合仓库内AuthenticateActionProvider、策略解析器与验收测试的源码实现逐行拆解认证 action 的工作原理并说明在现代中间件序列middleware-based sequence下如何免手动接入、直接获得自动化的请求认证能力。读完本文你将能够理解认证 action 的内部调用链与错误处理策略、明白序列模式与中间件模式的取舍以及在自己的控制器中正确使用authenticate()装饰器与SecurityBindings.USER注入。图片来源packages/authentication/docs/imgs/auth-framework-architecture.png描述了认证框架中 action、strategy、service、controller 各自的职责边界。认证 Action 在认证组件中的定位在开始之前需要先厘清认证 action 在整个认证组件中的位置。AuthenticationComponent作为组件向应用暴露了四类能力见 authentication.component.tsAuthenticateActionProvider—— 提供authentication.actions.authenticate绑定即本文的主角认证 actionAuthenticationStrategyProvider—— 提供authentication.strategy绑定负责按名称解析认证策略AuthMetadataProvider—— 提供authentication.operationMetadata绑定负责读取控制器方法上的认证元数据AuthenticationMiddlewareProvider—— 提供middleware.authentication绑定是中间件模式的入口内部直接委托给认证 action。这里需要特别注意原文档开篇的加粗提示对于基于中间件的序列middleware-based sequence即 LoopBack 4 默认的MiddlewareSequence而言本节内容并不需要你手动接入——认证中间件会被自动发现并添加到序列中。只有当你使用旧式的自定义SequenceHandler时才需要像下文演示的那样手动把认证 action 注入并调用。认证 Action 的接口契约认证 action 的类型定义位于 types.ts/** * interface definition of a function which accepts a request * and returns an authenticated user */ export interface AuthenticateFn { (request: Request): PromiseUserProfile | undefined; }它接收一个来自 REST 层的Request即 Express 风格的请求对象异步返回两种结果之一UserProfile—— 认证成功表示当前请求对应的已登录用户undefined—— 当前操作不需要认证例如控制器方法上没有authenticate装饰器。返回值会被绑定到SecurityBindings.USER下游的控制器函数、拦截器都可以通过依赖注入DI直接拿到当前用户。认证 Action 核心实现逐行解析原文档 authentication-action.md 给出了一份精简的伪代码完整呈现如下import HttpErrors from http-errors; async action(request: Request): PromiseUserProfile | undefined { const authStrategy await this.getAuthStrategy(); if (!authStrategy) { // The invoked operation does not require authentication. return undefined; } try { const userProfile: UserProfile await authStrategy.authenticate(request); this.setCurrentUser(userProfile); // a convenient return for the next request handlers return userProfile; } catch (err) { // interpret the raw error code/msg here and throw the corresponding HTTP error // convert it to http error if (err.code 401) { throw new HttpErrors.Unauthorized(err.message); } } }这份伪代码勾勒出认证 action 的三个核心步骤解析策略通过getAuthStrategy()获取当前请求对应的认证策略。若解析结果为undefined说明被调用的操作不需要认证直接返回undefined放行执行认证调用authStrategy.authenticate(request)校验凭据成功后得到userProfile绑定用户并返回通过setCurrentUser(userProfile)把用户画像绑定到请求上下文对应SecurityBindings.USER并作为返回值供后续处理器使用异常转换把策略抛出的底层错误码解释并转换为对应的 HTTP 错误示例中为 401 Unauthorized。与真实实现AuthenticateActionProvider的对照伪代码只是骨架仓库中真正的实现位于 auth-action.provider.ts 的AuthenticateActionProvider。其构造函数通过inject.getter()注入了一个延迟解析的策略 getter——这一点很关键inject.getter(AuthenticationBindings.STRATEGY) readonly getStrategies: Getter AuthenticationStrategy | AuthenticationStrategy[] | undefined , inject.setter(SecurityBindings.USER) readonly setCurrentUser: SetterUserProfile,由于 provider 在序列/中间件构造阶段就被实例化而此时当前路由从而认证元数据尚未确定因此必须注入 getter 函数把策略的解析推迟到action()真正执行的那一刻。value()方法把action包装成标准的AuthenticateFnvalue(): AuthenticateFn { return request this.action(request); }真实实现较伪代码还多了三处重要演进值得展开其一支持多策略。现代实现允许authenticate(a, b)声明多个策略action()会把策略统一转为数组并逐个尝试auth-action.provider.ts// convert to array if required strategies Array.isArray(strategies) ? strategies : [strategies]; const authErrors: unknown[] []; for (const strategy of strategies) { let authResponse: UserProfile | RedirectRoute | undefined undefined; try { authResponse await strategy.authenticate(request); } catch (err) { if (this.options.failOnError) { throw err; } authErrors.push(err); } // ... }默认情况下某个策略抛错会被收集起来继续尝试下一个只要其中一个策略成功错误即被丢弃。只有当全部策略都失败时才会抛出收集到的第一个错误。若希望“一票否决”可以开启failOnError选项见 types.ts 中的AuthenticationOptionsexport interface AuthenticationOptions { defaultMetadata?: AuthenticationMetadata[]; failOnError?: boolean; }其二支持 OAuth2 重定向。策略的authenticate()除了返回UserProfile还可以返回RedirectRoute。action 检测到重定向结果后会把目标 URL 与状态码通常为 302/303通过setRedirectUrl/setRedirectStatus绑定到上下文由控制器决定如何执行实际跳转见 keys.ts 中的AUTHENTICATION_REDIRECT_URL与AUTHENTICATION_REDIRECT_STATUS绑定。其三统一的兜底错误。若所有策略都返回了undefined即没有返回用户画像action 会抛出一个携带USER_PROFILE_NOT_FOUND错误码的非协议错误由上层统一映射为 HTTP 401const error new Error( User profile not returned from strategys authenticate function, ); Object.assign(error, {code: USER_PROFILE_NOT_FOUND}); throw error;策略解析与元数据Action 背后的两个 Provider认证 action 自身的逻辑并不复杂真正的“按需分派”发生在它背后的两个 provider 上。AuthMetadataProvider读取authenticate元数据AuthMetadataProvider 注入当前控制器类与方法名从反射元数据中读取认证信息value(): AuthenticationMetadata[] | undefined { if (!this.controllerClass || !this.methodName) return; const metadata getAuthenticateMetadata( this.controllerClass, this.methodName, ); // Skip authentication if skip is true if (metadata?.[0]?.skip) return undefined; if (metadata) return metadata; // Fall back to default metadata return this.options.defaultMetadata; }这里包含了三个语义方法上没有authenticate装饰器时返回undefined请求匿名放行装饰器带skip: true时同样跳过认证这正是authenticate.skip()糖装饰器的实现基础见 authenticate.decorator.ts可以配置组件的defaultMetadata为所有未装饰的方法设置默认认证策略形成“默认全站认证、个别放行”的策略。getAuthenticateMetadata的实现遵循“方法级优先、类级兜底”的查找顺序authenticate.decorator.ts因此你既可以把authenticate(jwt)放在类上统一保护也可以在个别方法上用authenticate.skip()打开匿名入口。AuthenticationStrategyProvider扩展点按名查找AuthenticationStrategyProvider 被声明为一个扩展点extensionPoint所有认证策略都以扩展的方式注册进来extensionPoint( AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME, {scope: BindingScope.TRANSIENT}, ) export class AuthenticationStrategyProvider implements ProviderAuthenticationStrategy[] | undefined { constructor( extensions() protected authenticationStrategies: GetterAuthenticationStrategy[], inject(AuthenticationBindings.METADATA) protected metadata?: AuthenticationMetadata[], ) {} // ... }策略的注册入口是 types.ts 中的registerAuthenticationStrategy(context, strategyClass)它把策略类作为扩展添加到authentication.strategies命名空间asAuthStrategy则是一个供injectable()使用的绑定模板同文件 L136-L144。策略按name属性被匹配——这正是 authentication-strategy.md 中AuthenticationStrategy接口要求每个策略必须有唯一name的原因。若找不到对应名称则抛出携带AUTHENTICATION_STRATEGY_NOT_FOUND错误码的错误const error new Error(The strategy ${name} is not available.); Object.assign(error, {code: AUTHENTICATION_STRATEGY_NOT_FOUND}); throw error;AuthenticationStrategy接口速览任何策略只需实现两个成员见 types.tsexport interface AuthenticationStrategy { name: string; authenticate( request: Request, ): PromiseUserProfile | RedirectRoute | undefined; }策略类可以通过构造函数注入全局或请求级的options以及任意所需服务例如负责从请求中抽取凭据的 service。仓库测试夹具提供了两个可直接参考的完整策略实现basic-strategy.ts从Authorization: Basic xxx头解析用户名/密码与 jwt-strategy.ts从Authorization: Bearer xxx头解析并校验 JWT它们同时实现了OASEnhancer自动把 security scheme 合并进 OpenAPI 规格。错误处理错误码到 HTTP 状态码的映射认证 action 内部只会抛出两类“非协议”错误它们需要在上层被转换为 HTTP 语义AUTHENTICATION_STRATEGY_NOT_FOUND—— 策略名称未注册USER_PROFILE_NOT_FOUND—— 策略返回的用户画像为空。这两类错误码定义于 types.ts。注意策略实现如 basic/jwt 策略自身抛出的错误通常已经带有statusCode它们直接抛HttpErrors.Unauthorized无需二次转换只有上述两个“框架级”错误需要映射。旧式序列模式的手动映射在自定义SequenceHandler中你需要在catch分支手动补上statusCode: 401。仓库测试夹具 authentication.sequence.ts 展示了完整写法async handle(context: RequestContext) { try { const {request, response} context; const route this.findRoute(request); // call authentication action await this.authenticateRequest(request); // Authentication successful, proceed to invoke controller const args await this.parseParams(request, route); const result await this.invoke(route, args); this.send(response, result); } catch (error) { if ( error.code AUTHENTICATION_STRATEGY_NOT_FOUND || error.code USER_PROFILE_NOT_FOUND ) { Object.assign(error, {statusCode: 401 /* Unauthorized */}); } this.reject(context, error); return; } }其中authenticateRequest通过inject(AuthenticationBindings.AUTH_ACTION)注入见 keys.ts 中AUTH_ACTION绑定的示例注释。中间件模式的自动映射如果你使用的是中间件序列默认推荐这一切都不需要自己写。AuthenticationMiddlewareProvider 已经以中间件形式封装好了认证逻辑与错误映射injectable( asMiddleware({ group: RestMiddlewareGroups.AUTHENTICATION, upstreamGroups: [RestMiddlewareGroups.FIND_ROUTE], }), ) export class AuthenticationMiddlewareProvider implements ProviderMiddleware { value(): Middleware { return async (ctx, next) { try { await this.authenticate(ctx.request); } catch (error) { if ( error.code AUTHENTICATION_STRATEGY_NOT_FOUND || error.code USER_PROFILE_NOT_FOUND ) { error.statusCode 401; } throw error; } return next(); }; } }该中间件被声明在RestMiddlewareGroups.AUTHENTICATION分组、且上游依赖FIND_ROUTE必须先路由才能确定控制器方法与认证元数据并通过asMiddleware自动发现机制加入默认的MiddlewareSequence。因此在中间件模式下你只需要加载AuthenticationComponent并注册策略认证即自动生效这正是本文开头原文档加粗提示的含义。端到端验证验收测试中的行为佐证认证 action 的各类行为都有对应的验收测试是理解其边界的绝佳材料Basic 认证成功/失败basic-auth-extension.acceptance.ts 验证了正确凭据返回用户 ID、缺失Authorization头返回 401message: Authorization header not found.、头格式不符非Basic前缀、分隔符缺失、多余段时的精确错误信息JWT 认证成功/失败jwt-auth-extension.acceptance.ts 覆盖了 token 过期Error verifying token : jwt expired、格式非法invalid token/jwt malformed等场景匿名放行两个测试文件中均包含“未加装饰器的方法允许匿名访问”的用例返回 200{running: true}印证了getAuthStrategy()返回undefined即放行的逻辑框架级错误映射两个文件都测试了未注册策略返回code: AUTHENTICATION_STRATEGY_NOT_FOUND、statusCode: 401以及策略返回空用户画像返回code: USER_PROFILE_NOT_FOUND、statusCode: 401的场景中间件序列验证basic-auth-extension.middleware.acceptance.ts 与 jwt-auth-extension.middleware.acceptance.ts 使用继承自MiddlewareSequence的 authentication.middleware.sequence.ts 完成同样的断言证明中间件模式下认证行为与序列模式完全一致且无需手工接线。测试中还演示了控制器如何消费认证结果——通过inject(SecurityBindings.USER)注入当前用户画像get(/whoAmI) authenticate(jwt) whoAmI(inject(SecurityBindings.USER) userProfile: UserProfile) { return userProfile[securityId]; }登录与校验的完整 API 流程以 BasicAuth JWT 为例将认证 action 放到更大的图景中它只是登录/校验两条流程中的一个环节。仓库的 authentication-system.md 用 BasicAuth JWT 示例拆解了各部分职责登录流程Login认证 action解析元数据拿到策略 → 调用strategy.authenticate()→ 把返回值设为当前用户策略从传输层请求头或本地配置文件抽取凭据 → 调用UserService.verifyCredentials()校验并返回用户画像控制器函数调用TokenService.generateToken()生成 JWT 并序列化到响应sendaction 会按 OpenAPI 描述序列化。校验流程Verify认证 action同样先解析策略再调用authenticate()并绑定当前用户策略从请求中抽取访问令牌 → 调用TokenService.verifyToken()校验令牌并从其中解码出用户画像控制器函数直接处理注入的当前用户。与流程对应的两个服务抽象位于 user.service.tsverifyCredentialsconvertToUserProfile凭据形态因策略而异本地账号为{username, password}、OAuth 为{clientId, clientSecret, callbackURL}、SAML 为{path, issuer, entryPoint}与 token.service.tsverifyToken/generateToken/ 可选的revokeToken。测试夹具 jwt-service.ts 给出了基于jsonwebtoken的完整可运行实现包括将[securityId]符号从 token 载荷中重建的细节。相关文档与源码索引要继续深挖仓库内准备了整套配套材料认证框架总览authentication-system.md多策略架构、登录/校验流程、token 与 session 两种跟踪方式策略接口定义authentication-strategy.md策略伪代码strategies/basic-auth.md、strategies/jwt.md、strategies/oauth2.md控制器端点定义controller-functions.md核心源码auth-action.provider.ts、auth-strategy.provider.ts、auth-metadata.provider.ts、authenticate.decorator.ts、keys.ts、types.ts组件装配authentication.component.ts验收测试basic-auth-extension.acceptance.ts、jwt-auth-extension.acceptance.ts 及对应的 middleware 版本。总而言之认证 action 是 LoopBack 4 认证体系里“承上启下”的一环向下通过策略扩展点按需分派校验向上把用户画像绑定到请求上下文供 DI 注入。对绝大多数新应用而言加载组件 注册策略 在控制器上使用authenticate()即可获得完整认证能力认证 action 本身由自动发现的中间件代为驱动无需手工介入。赞分享后端微服务【免费下载链接】loopback-nextLoopBack makes it easy to build modern API applications that require complex integrations.项目地址https://gitcode.com/gh_mirrors/lo/loopback-next点击查看免费下载相关推荐LoopBack 4 认证组件loopback/authentication深度指南装饰器、策略扩展点与中间件执行流LoopBack 4 认证组件loopback/authentication深度指南装饰器、策略扩展点与中间件执行流 导读 loopback/auth后端微服务LoopBack 4 JWT 认证扩展 loopback/authentication-jwt从原型到组件的演进与实现解析LoopBack 4 JWT 认证扩展 loopback/authentication jwt从原型到组件的演进与实现解析 loopback/authen后端微服务LoopBack 4 认证动作Authentication Action实战为自定义 Sequence 接入 authenticate 动作并保护 REST 端点LoopBack 4 认证动作Authentication Action实战为自定义 Sequence 接入 authenticate 动作并保护 RES后端微服务上一篇res-downloader教程从本地代理到无水印视频下载10分钟跑通第一次下一篇Browser-Use 快速上手AI 浏览器自动化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考