后端微服务【免费下载链接】loopback-nextLoopBack makes it easy to build modern API applications that require complex integrations.项目地址https://gitcode.com/gh_mirrors/lo/loopback-next点击查看免费下载本文聚焦 LoopBack 4 认证体系中最核心的接入点——认证动作Authentication Action在基于Sequence序列的请求处理管线中authenticate默认并不存在需要开发者通过AuthenticateActionProvider提供的AuthenticateFn手动注入到自定义序列中。读完本文你将掌握默认序列的执行步骤、AuthenticateActionProvider的底层机制策略延迟解析、用户 Profile 写入上下文、协议无关错误码并能在自己的应用中写出一个可运行、可绑定、可返回 401 的自定义认证序列同时了解新版本中间件式序列下认证中间件的等价实现。理解认证动作与 Sequenceauthenticate 为什么默认缺席在 LoopBack 4 的 REST 应用中每一个请求都会经过一组无状态动作的编排这组动作被称为Sequence序列。序列本质上是一系列步骤用于控制RestServer如何响应传入请求。默认序列会注入并依次执行findRoute、parseParams、invoke、send、reject这五个动作。在 Todo 示例的序列文件 中可以找到默认序列的经典实现该示例当前版本已切换为MiddlewareSequence但其对五个动作的语义描述仍然成立export class DefaultSequence implements SequenceHandler { /** * Constructor: Injects findRoute, invokeMethod logError * methods as promises. * * param {FindRoute} findRoute Finds the appropriate controller method, * spec and args for invocation (injected via SequenceActions.FIND_ROUTE). * param {ParseParams} parseParams The parameter parsing function (injected * via SequenceActions.PARSE_PARAMS). * param {InvokeMethod} invoke Invokes the method specified by the route * (injected via SequenceActions.INVOKE_METHOD). * param {Send} send The action to merge the invoke result with the response * (injected via SequenceActions.SEND) * param {Reject} reject The action to take if the invoke returns a rejected * promise result (injected via SequenceActions.REJECT). */ constructor( inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute, inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams, inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, inject(SequenceActions.SEND) public send: Send, inject(SequenceActions.REJECT) public reject: Reject, ) {} /** * Runs the default sequence. Given a handler context (request and response), * running the sequence will produce a response or an error. */ async handle(context: RequestContext): Promisevoid { try { const {request, response} context; const route this.findRoute(request); const args await this.parseParams(request, route); const result await this.invoke(route, args); debug(%s result -, route.describe(), result); this.send(response, result); } catch (error) { this.reject(context, error); } } }每个动作的职责如下动作职责findRoute找到合适的控制器方法、Swagger 规范与调用参数parseParams解析 HTTP 请求得到 API 参数列表invoke调用 Application Controller 中定义的 APIsend将 API 的返回结果写入 HTTP 响应reject上述任一步骤出错时捕获错误并记录日志关键点默认情况下authenticate不是动作序列的一部分。因此如果你要为端点启用认证必须修改默认序列把认证动作加进去。注意如果你使用的是基于中间件的序列middleware-based sequence则不需要本节的操作——认证由自动发现并加入序列的中间件强制执行。详见本文「中间件式序列下的认证」一节与 REST 中间件序列文档。AuthenticateActionProvider认证动作的提供者与底层机制认证动作AuthenticateFn由AuthenticateActionProvider类提供该类的核心实现在 auth-action.provider.ts。原文档给出的机制说明代码如下// ------ CODE THAT EXPLAINS THE MECHANISM --------- export class AuthenticateActionProvider implements ProviderAuthenticateFn { constructor( // The provider is instantiated for Sequence constructor, // at which time we dont have information about the current // route yet. This information is needed to determine // what auth strategy should be used. // To solve this, we are injecting a getter function that will // defer resolution of the strategy until authenticate() action // is executed. inject.getter(AuthenticationBindings.STRATEGY) readonly getStrategy: GetterAuthenticationStrategy, inject.setter(SecurityBindings.USER) readonly setCurrentUser: SetterUserProfile, ) {} /** * returns authenticateFn */ value(): AuthenticateFn { return request this.action(request); } /** * The implementation of authenticate() sequence action. * param request The incoming request provided by the REST layer */ async action(request: Request): PromiseUserProfile | undefined { const strategy await this.getStrategy(); if (!strategy) { // The invoked operation does not require authentication. return undefined; } const userProfile await strategy.authenticate(request); if (!userProfile) { // important to throw a non-protocol-specific error here let error new Error( User profile not returned from strategys authenticate function, ); Object.assign(error, { code: USER_PROFILE_NOT_FOUND, }); throw error; } this.setCurrentUser(userProfile); return userProfile; } }机制拆解如下value()返回一个类型为AuthenticateFn的函数该函数尝试获取认证策略由AuthenticationStrategyProvider通过AuthenticationBindings.STRATEGY绑定解析得到。如果该端点没有指定认证策略认证动作立即返回undefined请求继续往下走。如果该端点指定了认证策略则调用策略的authenticate(request)方法。若返回了用户 Profile说明认证成功该 Profile 会通过SecurityBindings.USER绑定写入请求上下文否则抛出USER_PROFILE_NOT_FOUND错误。之所以在构造函数中注入的是inject.getterGetter 函数而非直接注入策略实例是因为 Provider 在序列构造时就被实例化此时还不知道当前路由信息而路由信息恰恰决定了应使用哪个策略。通过延迟解析直到authenticate()动作真正执行时才解析策略。现代源码的增强多策略、failOnError 与重定向当前仓库中的AuthenticateActionProvider见 auth-action.provider.ts在原始机制之上支持了更多能力构造函数注入了策略 Getter、当前用户 Setter、重定向 URL/状态 Setter以及组件配置AuthenticationOptionsexport class AuthenticateActionProvider implements ProviderAuthenticateFn { constructor( inject.getter(AuthenticationBindings.STRATEGY) readonly getStrategies: Getter AuthenticationStrategy | AuthenticationStrategy[] | undefined , inject.setter(SecurityBindings.USER) readonly setCurrentUser: SetterUserProfile, inject.setter(AuthenticationBindings.AUTHENTICATION_REDIRECT_URL) readonly setRedirectUrl: Setterstring, inject.setter(AuthenticationBindings.AUTHENTICATION_REDIRECT_STATUS) readonly setRedirectStatus: Setternumber, config({fromBinding: AuthenticationBindings.COMPONENT}) private readonly options: AuthenticationOptions {}, ) {} value(): AuthenticateFn { return request this.action(request); } async action(request: Request): PromiseUserProfile | undefined { let strategies await this.getStrategies(); if (strategies null) { // The invoked operation does not require authentication. return undefined; } // 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); } // response from strategy.authenticate() could return an object of // type UserProfile or RedirectRoute if (RedirectRoute.isRedirectRoute(authResponse)) { const redirectOptions authResponse; // bind redirection url and status to the context // controller should handle actual redirection this.setRedirectUrl(redirectOptions.targetLocation); this.setRedirectStatus(redirectOptions.statusCode); return; } else if (authResponse ! null) { // if strategy.authenticate() returns an object of type UserProfile, // set it as current user const userProfile authResponse as UserProfile; this.setCurrentUser(userProfile); return userProfile; } } if (authErrors.length) { throw authErrors[0]; } // important to throw a non-protocol-specific error here const error new Error( User profile not returned from strategys authenticate function, ); Object.assign(error, { code: USER_PROFILE_NOT_FOUND, }); throw error; } }现代版本的行为要点支持多个策略一个端点可绑定多个策略认证动作会按顺序逐个调用只要有一个策略返回UserProfile即认证成功。failOnError选项当某个策略抛出异常时默认行为是记录错误并继续尝试下一个策略若组件配置failOnError: true见 AuthenticationOptions 定义则立即抛出错误终止认证。支持RedirectRoute策略的authenticate()若返回RedirectRoute常用于 OAuth 登录跳转认证动作会将该重定向 URL 与状态码通常是 302/303绑定到上下文交由控制器处理实际跳转。对应绑定键AUTHENTICATION_REDIRECT_URL与AUTHENTICATION_REDIRECT_STATUS定义在 keys.ts。所有策略都失败且存在错误时抛出第一个错误没有任何策略返回 Profile 时抛出带code: USER_PROFILE_NOT_FOUND的协议无关错误。相关类型与绑定键AuthenticateFn类型定义在 types.ts(request: Request) PromiseUserProfile | undefined。AuthenticationStrategy接口types.ts要求实现name属性与authenticate(request)方法返回值可以是UserProfile、RedirectRoute或undefined。绑定键AuthenticationBindings.AUTH_ACTION定义在 keys.ts其值为authentication.actions.authenticate用于从上下文获取认证函数。AuthenticationComponentauthentication.component.ts会把AuthenticateActionProvider注册到AUTH_ACTION绑定键上同时注册策略提供者与元数据提供者。单元测试佐证仓库中的单元测试 authentication.provider.unit.ts 验证了上述机制value()返回的函数能认证请求并返回用户returns a function which authenticates a request and returns a user。认证成功后更新当前用户updates current user。所有策略都失败时抛出错误只要有一个策略成功则不抛错failOnError: true时任一策略失败即抛错。未返回用户 Profile 时抛出code: USER_PROFILE_NOT_FOUND的错误对应测试throws USER_PROFILE_NOT_FOUND error when userprofile not returned。自定义序列接入认证动作MyAuthenticatingSequence 实战下面是一个改造后的自定义序列它在invoke之前调用了authenticate动作。这是原文档给出的核心示例也是仓库测试夹具 authentication.sequence.ts 中真实使用的模式export class MyAuthenticatingSequence implements SequenceHandler { constructor( // ... Other injections // ------ ADD SNIPPET --------- inject(AuthenticationBindings.AUTH_ACTION) protected authenticateRequest: AuthenticateFn, // ------------- END OF SNIPPET ------------- ) {} async handle(context: RequestContext) { try { const {request, response} context; const route this.findRoute(request); // ------ ADD SNIPPET --------- //call authentication action await this.authenticateRequest(request); // ------------- END OF SNIPPET ------------- // 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) { // ------ ADD SNIPPET --------- if ( error.code AUTHENTICATION_STRATEGY_NOT_FOUND || error.code USER_PROFILE_NOT_FOUND ) { Object.assign(error, {statusCode: 401 /* Unauthorized */}); } // ------------- END OF SNIPPET ------------- this.reject(context, error); return; } } }注意到序列构造函数中新增的依赖注入inject(AuthenticationBindings.AUTH_ACTION) protected authenticateRequest: AuthenticateFn,绑定键AuthenticationBindings.AUTH_ACTION让我们拿到由AuthenticateActionProvider提供的、类型为AuthenticateFn的认证函数authenticateRequest。使用要点位置认证函数authenticateRequest可以在自定义序列中任何位于invoke动作之前的地方调用从而保护端点。错误码映射AUTHENTICATION_STRATEGY_NOT_FOUND与USER_PROFILE_NOT_FOUND是两个协议无关protocol-agnostic的错误必须在序列中捕获并赋予 HTTP 状态码401Unauthorized。具体返回什么 HTTP 错误码由开发者在自己实现的认证策略或其自定义服务中决定。失败即短路认证过程中一旦抛出任何错误端点的控制器函数永远不会被执行。策略实现自身抛出的错误通常已带有statusCode只有上述两个协议无关错误需要序列手动补上statusCode: 401。仓库测试夹具 authentication.sequence.ts 的注释明确说明了这一设计意图。在 passport-login 示例 中可以看到一个更复杂的真实场景该序列把authenticateRequest放在parseParams之后调用因为登录场景需要先从路径参数中解析出 OAuth 提供者名称再把oauth2-provider-name写入 query最后才执行认证认证失败且请求路径为/login时则把UnauthorizedError事件交回给外层 Express 应用处理。将认证序列绑定到应用定义好执行认证动作的自定义序列后必须把它绑定到应用application.ts上export class MyApplication extends BootMixin( ServiceMixin(RepositoryMixin(RestApplication)), ) { constructor(options?: ApplicationConfig) { super(options); //... // ------ ADD SNIPPET --------- this.sequence(MyAuthenticatingSequence); // ------------- END OF SNIPPET ------------- //... } }一个完整的认证应用配置通常包含三部分可参考 Authentication-component-options.md 的总结加载认证组件、注册认证策略、绑定认证序列import { AuthenticationComponent, registerAuthenticationStrategy, } from loopback/authentication; export class MyApplication extends BootMixin( ServiceMixin(RepositoryMixin(RestApplication)), ) { constructor(options?: ApplicationConfig) { super(options); //... // ------ ADD SNIPPET --------- // load the authentication component this.component(AuthenticationComponent); // register your custom authentication strategy registerAuthenticationStrategy(this, BasicAuthenticationStrategy); // use your custom authenticating sequence this.sequence(MyAuthenticatingSequence); // ------------- END OF SNIPPET ------------- } }在 JWT 认证教程对应示例 todo-jwt中你还可以看到完整的端到端流程加载AuthenticationComponent与JWTAuthenticationComponent、在序列中启用 JWT 认证动作、创建登录/注册控制器、用authenticate(jwt)保护 Todo 接口。中间件式序列下的认证AuthenticationMiddlewareProvider原文档开头有一条重要提示如果应用使用基于中间件的序列middleware-based sequence则无需手动添加认证动作因为认证由自动发现并加入序列的中间件强制执行。loopback/rest自 6.0.0 起默认采用中间件式序列MiddlewareSequencelb4 app生成的src/sequence.ts即继承它参见 REST 中间件序列文档。loopback/authentication为此提供了AuthenticationMiddlewareProvider与AuthenticateActionProvider同文件见 auth-action.provider.tsinjectable( asMiddleware({ group: RestMiddlewareGroups.AUTHENTICATION, upstreamGroups: [RestMiddlewareGroups.FIND_ROUTE], }), ) export class AuthenticationMiddlewareProvider implements ProviderMiddleware { constructor( inject(AuthenticationBindings.AUTH_ACTION) private authenticate: AuthenticateFn, ) {} 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组并声明findRoute为其上游组。它内部复用同一个AUTH_ACTION绑定的AuthenticateFn并在捕获两个协议无关错误时自动补上statusCode 401——这正是自定义序列中那段错误映射代码的中间件版等价实现。上图展示了中间件式序列中各组件的执行顺序sendResponse最先进入负责收集下游结果或捕获错误、随后依次执行cors、apiSpec、middleware、findRoute、authentication、parseParams、invokeMethod。认证中间件在findRoute之后、parseParams之前执行与自定义序列中认证动作放在 invoke 之前的位置语义一致。因此在新生成的应用中你通常什么都不用做加载AuthenticationComponent后认证中间件会被自动发现并加入序列。只有当你的应用仍使用传统动作式序列例如需要精细控制每个动作的执行时机、或从旧版本迁移而来才需要采用本文第三、四节的自定义序列方案。错误码约定与协议无关设计认证体系特意设计为协议无关即认证动作本身不直接与 HTTP 状态码耦合而由外层序列或中间件负责映射。仓库中定义了两个错误码常量types.tsAUTHENTICATION_STRATEGY_NOT_FOUND策略解析失败时抛出。由 auth-strategy.provider.ts 在按名字查找不到策略时生成错误消息形如The strategy doesnotexist is not available.。USER_PROFILE_NOT_FOUND策略执行完成但没有返回用户 Profile 时抛出错误消息为User profile not returned from strategys authenticate function。在自定义序列或认证中间件中这两个错误应被映射为 HTTP 401。仓库的接受测试 basic-auth-extension.acceptance.ts 验证了完整行为携带正确 Basic 凭据访问/whoAmI返回用户 ID缺少Authorization头时返回statusCode: 401的UnauthorizedError使用不存在的策略名authenticate(doesnotexist)时返回code: AUTHENTICATION_STRATEGY_NOT_FOUND、statusCode: 401策略返回undefined时返回code: USER_PROFILE_NOT_FOUND、statusCode: 401未加authenticate装饰器的方法允许匿名访问认证动作因没有策略而直接返回。延伸阅读认证装饰器Authentication Decoratorauthenticate的语法与元数据即端点如何声明需要认证。认证策略Authentication Strategy自定义策略的实现与注册。管理自定义认证策略选项defaultMetadata、failOnError等组件配置。REST 中间件序列中间件式序列的组排序与扩展方式。JWT 认证教程完整可运行的 JWT 认证应用。认证装饰器参考authenticate装饰器的详细 API。赞分享后端微服务【免费下载链接】loopback-nextLoopBack makes it easy to build modern API applications that require complex integrations.项目地址https://gitcode.com/gh_mirrors/lo/loopback-next点击查看免费下载相关推荐Redwood 自定义认证Custom Authentication集成指南以 Nhost 为例接入任意 Auth ProviderRedwood 自定义认证Custom Authentication集成指南以 Nhost 为例接入任意 Auth Provider 当 Redwood后端前端Web框架开发工具LoopBack 4 高效传输请求体loopback/rest-msgpack 组件接入与实战指南LoopBack 4 高效传输请求体loopback/rest msgpack 组件接入与实战指南 导读 本文讲解 LoopBack 4 生态中的 loo后端微服务InvenTree Action 插件开发指南基于 ActionMixin 与 /api/action/ 端点构建自定义动作InvenTree Action 插件开发指南基于 ActionMixin 与 /api/action/ 端点构建自定义动作 本文面向 InvenTree 插后端前端企业应用ERP上一篇Spring AI流式模式下Token计算的3种高效解决方案下一篇百度网盘提取码效率工具快速获取分享资源的实用方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考