GraphQL Scala(Sangria)认证与授权实战:ExceptionHandler、FieldTag 与 Middleware 完整实现指南

发布时间:2026/9/25 3:07:42

GraphQL Scala(Sangria)认证与授权实战:ExceptionHandler、FieldTag 与 Middleware 完整实现指南 【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载导读本文基于 HowToGraphQL 仓库的 GraphQL Scala 认证章节系统讲解如何在基于 Akka HTTP Sangria Slick 的 GraphQL 服务中实现完整的认证与授权能力通过 email/password 登录、自定义异常处理、利用FieldTag标记受保护字段以及借助Middleware在字段解析前统一拦截校验。读完本文你将掌握 Sangria 中ExceptionHandler、UpdateCtx、Middleware三个核心机制的组合用法能够为任何基于 meta/structure.graphql 风格的 HackerNews 类 Schema 加上可扩展的登录与权限控制。1. 认证场景分析目标与最坏情况在真实项目中绝大多数 API 都是需要安全保护的服务端必须校验客户端是否具备读写数据的权限绝不能允许任何人匿名地向服务添加数据。GraphQL 服务也不例外。本教程要达成的目标有两个提供使用email 和 password 进行登录sign in的能力保护secure查询——对于标记为需要登录的字段校验用户是否已登录。同时认证/授权引擎必须能处理两种最坏情况用户在登录时提供了错误的凭据email 与 password 不匹配用户未登录却访问了受保护的查询。Sangria 处理这类错误情况的官方做法是抛出Exception并在顶层用对应的处理器捕获。下面我们按这个思路逐步实现。从仓库前序章节可以回顾整个上下文项目的入口结构在 1-getting-started.md 中说明Server.scala负责 HTTP 层GraphQLServer.scala负责调用 SangriaExecutorDAO.scala负责数据库访问DBSchema.scala负责表结构与种子数据2-preparing-first-query.md 中定义了贯穿所有查询的MyContext(dao)上下文对象8-mutations.md 中实现了createUser、createLink、createVote等 mutation为本章的loginmutation 提供了直接基础。2. 定义异常类型AuthenticationException 与 AuthorizationException第一步是在models包中定义两个异常类分别对应上述两种最坏情况case class AuthenticationException(message: String) extends Exception(message) case class AuthorizationException(message: String) extends Exception(message)AuthenticationException用于登录sign in阶段当提供的email与password与数据库中的既有用户不匹配时抛出AuthorizationException用于授权阶段当受保护的查询在缺少凭据用户未登录的情况下被访问时抛出。语义区分AuthenticationException回答你是谁认证AuthorizationException回答你有没有权限授权。二者分开定义便于在后续章节中针对不同的错误分别处理或返回不同的错误信息。3. 自定义 ExceptionHandler将异常转为 JSON 响应有了异常类接下来需要实现一个自定义的ExceptionHandler。Sangria 的ExceptionHandler需要一个偏函数partial function把异常类型转换为HandledException随后 Sangria 会在内部将HandledException转换为符合 GraphQL 规范的 JSON 错误响应并返回给客户端。在GraphQLServer中添加如下代码//add to imports: import com.howtographql.scala.sangria.models.{AuthenticationException, AuthorizationException} import sangria.execution.{ExceptionHandler EHandler, _} //later in the body val ErrorHandler EHandler { case (_, AuthenticationException(message)) ⇒ HandledException(message) case (_, AuthorizationException(message)) ⇒ HandledException(message) }两个实现要点EHandler的偏函数签名形如(marshaller, exception) HandledException因此第一个模式是通配符_第二个模式才是我们要捕获的异常类型由于作用域中已经存在另一个名为ExceptionHandler的类这里通过import sangria.execution.{ExceptionHandler EHandler, _}做导入别名避免命名冲突。你也可以用自己习惯的方式管理这个冲突例如直接全限定名引用。3.1 将 Handler 接入 Executor异常处理器定义好后还必须注册到Executor中才会生效。在GraphQLServer的executeGraphQLQuery中通过exceptionHandler参数传入Executor.execute( GraphQLSchema.SchemaDefinition, query, MyContext(dao), variables vars, operationName operation, deferredResolver GraphQLSchema.Resolver, exceptionHandler ErrorHandler ).map(OK - _) .recover { case error: QueryAnalysisError BadRequest - error.resolveError case error: ErrorWithResolver InternalServerError - error.resolveError }这个Executor的完整形态与2-preparing-first-query.md 中最初的版本相比新增了两个参数deferredResolver GraphQLSchema.Resolver用于Fetcher的批量去重解析详见 4-deferred-resolvers.mdexceptionHandler ErrorHandler即本节定义的异常处理器。recover分支仍然负责把 Sangria 的查询分析错误QueryAnalysisError对应 400与执行期错误ErrorWithResolver对应 500转换为合适的 HTTP 状态码。4. 登录Sign inFieldTag 标记受保护字段要实现登录动作需要回答三个问题需要一个用户可以用来认证的端点endpoint——也就是loginmutation需要一种方式保存用户是否已正确登录的信息——使用贯穿查询的MyContext需要某种机制判断某个端点是否需要授权——使用 Sangria 的FieldTag。4.1 FieldTag 原理Sangria 可以给查询中的每一个字段打标签tag标签用途非常广泛。在我们的场景中可以用一个自定义标签来标记这个字段是受保护的。创建标签只需要一个继承FieldTagtrait 的类即可。在models包中创建Authorized标签//add to imports: import sangria.execution.FieldTag //bottom of the body case object Authorized extends FieldTag4.2 标记受保护的 createLink mutation现在我们就可以给字段打标签了。本例将addLink即createLinkmutation 设为受保护字段——只有登录用户才能创建链接。做法是在Field定义中增加tags属性Field(createLink, LinkType, arguments UrlArg :: DescArg :: PostedByArg :: Nil, tags Authorized :: Nil, resolve c c.ctx.dao.createLink(c.arg(UrlArg), c.arg(DescArg), c.arg(PostedByArg))),完整 mutation 字段定义与 8-mutations.md 中createLink的唯一区别就是多了tags Authorized :: Nil这一行。注意createLink的三个参数UrlArg、DescArg、PostedByArg分别对应urlString、descriptionString、postedByIdInt是从 mutation 章节沿用下来的Argument常量。关键认知字段被打上标签后Sangria并不会自动执行任何校验——FieldTag本质上是信息性的具体逻辑需要你自己实现。Sangria 会在执行到该字段时把标签信息暴露给中间件真正的拦截动作由后面的Middleware完成。5. 用 MyContext 保存登录态login 与 ensureAuthenticatedSangria 允许同一个context 对象贯穿整次查询执行——每个后续字段的 resolver 都能访问到它。这恰好符合保存当前用户的需求。扩展MyContext加入当前用户信息与两个辅助函数package com.howtographql.scala.sangria import com.howtographql.scala.sangria.models.{AuthenticationException, AuthorizationException, User} import scala.concurrent._ import scala.concurrent.duration.Duration case class MyContext(dao: DAO, currentUser: Option[User] None){ def login(email: String, password: String): User { val userOpt Await.result(dao.authenticate(email, password), Duration.Inf) userOpt.getOrElse( throw AuthenticationException(email or password are incorrect!) ) } def ensureAuthenticated() if(currentUser.isEmpty) throw AuthorizationException(You do not have permission. Please sign in.) }逐项说明currentUser: Option[User] None保存当前登录用户。None表示未登录登录成功后为Some(user)。这里复用 2-preparing-first-query.md 中创建的MyContext(dao)只是新增了第二个带默认值的构造参数因此现有调用MyContext(dao)仍然兼容login(email, password)登录辅助函数。调用dao.authenticate查库凭据匹配则返回User不匹配则抛出本章开头定义的AuthenticationExceptionensureAuthenticated()检查currentUser是否为空为空则抛出AuthorizationException。实现提示示例中使用了Await.result(..., Duration.Inf)同步阻塞等待是为了保持代码简单。生产代码应避免使用Duration.Inf建议改为异步/超时可控的方式。5.1 DAO.authenticate数据库凭据校验login依赖DAO上的authenticate函数它负责在数据库中查找凭据匹配的用户def authenticate(email: String, password: String): Future[Option[User]] db.run { Users.filter(u u.email email u.password password).result.headOption }这里的Users是 6-interfaces.md 中在DBSchema定义的 SlickTableQueryu.email email u.password password是 Slick 的类型安全过滤条件headOption保证结果至多一个。返回Future[Option[User]]——匹配返回Some(user)无匹配返回None由MyContext.login决定是否抛出异常。6. login mutation用 UpdateCtx 在解析后更新上下文最后一个关键部件是loginmutation 本身。它需要两个参数email、password返回UserType并在解析成功后把登录用户写回 context供本次查询中后续字段使用。//before Mutation object definition: val EmailArg Argument(email, StringType) val PasswordArg Argument(password, StringType) //in Mutation definition Field(login, UserType, arguments EmailArg :: PasswordArg :: Nil, resolve ctx UpdateCtx( ctx.ctx.login(ctx.arg(EmailArg), ctx.arg(PasswordArg))){ user ctx.ctx.copy(currentUser Some(user)) } )这里的核心是UpdateCtx它是本次登录流程中最重要的新机制UpdateCtx接受两个函数作为参数第一个函数负责产生响应值。本例中ctx.ctx.login(...)返回User类型登录成功即返回用户、失败即抛异常第二个函数接收第一个函数的输出user并返回新的 context 类型ctx.ctx.copy(currentUser Some(user))这个新 context 会替换旧 context并用于后续所有字段的解析。也就是说login解析成功后本次查询中位于其后的createLink等字段拿到的ctx.ctx就是带有currentUser Some(user)的新上下文。与 8-mutations.md 中普通 mutation 的区别普通 mutation 的resolve直接返回数据或Future而login的resolve返回的是UpdateCtx动作——这是解析结果 上下文更新的复合动作。Schema 侧目标Mutation类型在 meta/structure.graphql 中的signinUser对应这里的login实现思路。至此loginmutation 已可成功执行。但createLink仍然对所有人开放——下一节用Middleware补上真正的拦截逻辑。7. Middleware在执行期统一拦截受保护字段7.1 Middleware 是什么Sangria 在执行查询期间提供了Middleware机制Middleware类在查询执行期间被调用若有多个Middleware它们会一个接一个地顺序执行你可以在字段解析前后甚至整条查询前后注入自定义逻辑最大的优势这类逻辑与业务代码完全解耦。例如可以用它做性能基准测试benchmarking并在生产环境关闭。本例中用Middleware捕获带Authorized标签的受保护字段在字段被解析之前若发现Authorized标签就检查用户是否已认证。7.2 实现 AuthMiddleware创建AuthMiddleware.scalapackage com.howtographql.scala.sangria import com.howtographql.scala.sangria.models.Authorized import sangria.execution.{Middleware, MiddlewareBeforeField, MiddlewareQueryContext} import sangria.schema.Context object AuthMiddleware extends Middleware[MyContext] with MiddlewareBeforeField[MyContext] { override type QueryVal Unit override type FieldVal Unit override def beforeQuery(context: MiddlewareQueryContext[MyContext, _, _]) () override def afterQuery(queryVal: QueryVal, context: MiddlewareQueryContext[MyContext, _, _]) () override def beforeField(queryVal: QueryVal, mctx: MiddlewareQueryContext[MyContext, _, _], ctx: Context[MyContext, _]) { val requireAuth ctx.field.tags contains Authorized //1 if(requireAuth) ctx.ctx.ensureAuthenticated() //2 continue //3 } }核心逻辑在beforeField中ctx.field.tags contains Authorized读取当前字段的FieldTag列表检查是否包含Authorized标签ctx.ctx.ensureAuthenticated()若字段受保护调用MyContext.ensureAuthenticated——用户已登录则通过未登录则抛出AuthorizationException该异常会被第 3 节的ErrorHandler捕获并转为 JSON 错误响应continue一切正常则放行Sangria 继续执行该字段的解析。beforeField返回continue是 Sangria 中间件允许继续执行字段的约定动作。Middleware[MyContext]的泛型参数指明了 context 类型MiddlewareBeforeField[MyContext]则要求实现beforeFieldQueryVal/FieldVal在此处仅为占位类型Unit因为本例不需要在查询/字段之间传递中间状态。7.3 把 Middleware 注册到 Executor最后一步是把中间件加入ExecutorExecutor.execute( GraphQLSchema.SchemaDefinition, query, MyContext(dao), variables vars, operationName operation, deferredResolver GraphQLSchema.Resolver, exceptionHandler GraphQLSchema.ErrorHandler, middleware AuthMiddleware :: Nil ).map//...注意这里exceptionHandler引用了GraphQLSchema.ErrorHandler与第 3 节定义的ErrorHandler等价新增了middleware AuthMiddleware :: Nil参数——middleware接受中间件列表因此用:: Nil构造单元素列表以后可以继续追加其他中间件。8. 端到端验证登录 创建链接现在createLinkmutation 已被保护必须先登录才能调用。GraphQL 允许在同一次 mutation 中先执行login再执行createLink利用UpdateCtx更新的上下文完成授权mutation loginAndAddLink { login( email:fredflinstones.com, password:wilmalove ){ name } createLink( url: howtographql.com, description: Great tutorial page, postedById: 2 ){ url description postedBy{ name } } }你可以对上面的查询做三组实验来验证整个链路正常路径凭据正确如示例中的fredflinstones.com/wilmalove对应 6-interfaces.md 中DBSchema预置的种子用户login返回用户名createLink正常执行并返回url、description及嵌套的postedBy.name错误凭据提供错误的 email 或 passwordlogin抛出AuthenticationException经ErrorHandler转换为包含email or password are incorrect!的 JSON 错误响应未登录访问跳过login直接执行createLinkbeforeField中的ensureAuthenticated抛出AuthorizationException响应为You do not have permission. Please sign in.。9. 扩展思路FieldTag、Token 与角色控制本章示例刻意保持了代码简洁但它展示的机制可以按需扩展从登录态取 userId 而非信任客户端入参createLink的postedById目前来自客户端参数更安全的做法是从ctx.ctx.currentUser中直接读取user.id避免客户端伪造归属改用 Token 认证不必每次查询都携带 email/password可以换成签发并校验 Token例如把currentUser替换为解析后的 Token 信息MyContext.login阶段负责换取 TokenensureAuthenticated负责校验 Token用 FieldTag 做角色Role控制Authorized只是一个例子你可以定义Admin、Moderator等更多FieldTag在Middleware.beforeField中根据标签组合检查用户角色实现细粒度权限叠加多个 Middlewaremiddleware AuthMiddleware :: Nil中的列表可以继续追加例如把日志、监控、限流等横切逻辑与认证逻辑各自封装为独立中间件。Sangria 的官方文档始终是最新的参考来源其中包含大量上述机制的更多示例。10. 本章小结通过本章的学习我们完成了两件事认证Authentication新增loginmutation用户可用 email/password 登录登录态保存在贯穿查询的MyContext.currentUser中授权Authorization用AuthorizedFieldTag 标记createLink用AuthMiddleware在字段解析前统一校验登录态未登录请求被AuthorizationException拒绝。支撑这套能力的 Sangria 三大机制及其分工是机制作用本章落点ExceptionHandler顶层捕获异常并转 JSON 错误响应ErrorHandler处理两类异常FieldTag给字段打上信息性标签Authorized标记createLinkMiddleware执行期横切逻辑、与业务解耦AuthMiddleware.beforeField拦截UpdateCtx则解决了mutation 成功后如何把新状态登录用户传播给本次查询后续字段的关键问题。相关章节与资料均为本仓库内文件Schema 全景与signinUser/createUser设计meta/structure.graphql、0-introduction.md项目初始化与application.conf数据库配置1-getting-started.mdMyContext与GraphQLServer/Executor的建立2-preparing-first-query.mdFetcher与deferredResolverExecutor中的既有参数4-deferred-resolvers.mdUser/Vote模型与HasId/Identifiable接口6-interfaces.md模型关系与Fetcher.rel7-relations.mdcreateLinkmutation 与MutationObjectType 定义8-mutations.md赞分享【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载相关推荐Sangria: Scala GraphQL 实现Sangria: Scala GraphQL 实现 项目基础介绍和主要编程语言 Sangria 是一个基于 Scala 编程语言的 GraphQL 实现。它旨在如何使用 Rich 美化终端输出Python 终端格式化完整指南如何使用 Rich 美化终端输出Python 终端格式化完整指南 Rich 是一个功能强大的 Python 库专为在终端中创建丰富文本和精美格式化输出而设计TWiLight Menu 终极指南让您的任天堂DS变身复古游戏中心TWiLight Menu 终极指南让您的任天堂DS变身复古游戏中心 想要为您的任天堂DS、DSi或3DS设备注入全新活力吗TWiLight Menu嵌入式系统编程上一篇突破图数据库性能瓶颈Dgraph查询优化的学术与实践解析下一篇拯救训练效率PEFT早停策略终结过拟合难题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/25 3:07:42

WOA-CNN-BiLSTM时间序列预测:鲸鱼算法自动调参实战

简介:这是一份面向数据分析师、机器学习工程师与金融、气象等领域从业者的Python完整项目资料,核心实现WOA-CNN-BiLSTM时间序列预测模型。资料采用鲸鱼优化算法自动搜寻CNN与双向LSTM的关键超参数,兼顾空间特征提取与长短期时序依赖建模&…

2026/9/25 3:57:44

网上订餐系统毕设实战:Spring Boot+Vue全栈开发与答辩指南

做毕设选这个题目,我先说个结论:网上订餐系统这个选题,放在Spring Boot Vue这套组合里,是当前性价比最高的方向之一。原因很简单,它不属于那种冷门小众的偏题,业务流程完整、角色划分清晰、技术栈主流&…

2026/9/25 3:57:44

高校选课系统开题答辩全攻略:从选题到防坑指南

开题答辩这件事,很多同学把它当成“走过场”——PPT念一遍,评委随便问两句,半小时就结束了。但等你真正站在讲台上,面对三位评委老师齐齐看向你的目光,才发现那些“随便问”的问题,每一条都踩在你的项目软肋…

2026/9/25 3:57:44

基于Spring Boot+Vue的数码产品对比平台:全栈开发与数据建模实战

二手手机怎么选才不会踩坑?笔记本标压和低压处理器到底差多少?这些问题的答案,本质上都指向同一个东西:可靠的参数数据与直观的横向对比。我最近用 Java、Spring Boot 和 Vue 落地了一个数码产品对比平台,正好把全栈开…

2026/9/25 3:57:44

Neo4j 5.26 Windows实战:安装配置、CSV导入与多跳查询

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 3:57:44

零代码API服务:从SQL到HTTP接口的原理、落地与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 20:24:47

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/23 12:06:55

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/25 0:02:35

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:02:35

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:02:35

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/22 16:34:32

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/22 20:01:30

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/22 13:25:41

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑