Authlib 中的 RFC9101(JAR)实现:JWT 安全授权请求(Request Object)完整实战指南
【免费下载链接】authlibThe ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS, JWE, JWK, JWA, JWT included.项目地址https://gitcode.com/gh_mirrors/au/authlib点击查看免费下载导读本文围绕 Authlib 对 RFC9101The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request简称 JAR 的通用实现展开。JAR 规范的核心思路是将 OAuth 2.0 授权请求的负载封装进一个 JWT称为Request Object通过request参数按值传递或通过request_uri参数按引用传递从而比直接把请求参数塞进 GET 查询串或 POST 表单更安全。读完本文你将掌握Authlib 中JWTAuthenticationRequest扩展的注册与子类化方法、四个必须覆写的钩子方法、require_signed_request_object在客户端元数据与服务器元数据两个层面的强制机制、完整的服务端校验流程与对应错误码以及基于测试用例的端到端验证方法。一、RFC9101 是什么把授权请求装进 JWT1.1 传统授权请求的安全短板在标准 OAuth 2.0RFC6749流程中客户端把response_type、client_id、redirect_uri、scope、state等参数直接放进授权端点authorization endpoint的 GET 查询字符串或 POST 表单。这带来两个问题请求负载明文暴露在浏览器历史、日志与中间设备中容易遭篡改或注入服务器无法验证请求参数确实来自已注册客户端、且在传输过程中未被改动。RFC9101JAR正是为缓解这些问题而设计的它把整个授权请求负载放进一个 JWTRequest Object由客户端签名后提交服务器验签后以 JWT 内的 claims 作为实际的授权请求负载。1.2 Request Object 的两种传递方式依据 docs/oauth2/specs/rfc9101.rst 的说明Request Object 有两种传递方式按值传递request参数客户端把签名后的 JWT 直接作为授权请求的request参数提交按引用传递request_uri参数客户端把 JWT 托管在某个 URL 上授权请求只携带request_uri由授权服务器在服务端获取该 JWT。这两种方式对应JWTAuthenticationRequest构造参数中的support_request与support_request_uri默认都为True。1.3 何时启用Request Object 是可选能力除非被强制启用。RFC9101 给出了两级强制开关Authlib 均完整支持客户端级客户端注册元数据中的require_signed_request_object为True时该客户端的授权请求必须使用 Request Object服务器级服务器元数据中的require_signed_request_object为True时所有客户端的授权请求都必须使用 Request Object。二、模块结构与 API 总览Authlib 将 RFC9101 的通用实现放在authlib/oauth2/rfc9101/目录下共四个源码文件文件职责authorization_server.pyJWTAuthenticationRequest扩展类负责解析、验签与校验 Request Objectregistration.pyClientMetadataClaims向 RFC7591 客户端注册协议扩展require_signed_request_object元数据discovery.pyAuthorizationServerMetadata向 RFC8414 服务器发现协议扩展require_signed_request_object元数据errors.py4 个与 JAR 相关的 OAuth 2.0 错误类模块统一从init.py 导出三个公开类from authlib.oauth2 import rfc9101 rfc9101.JWTAuthenticationRequest # 授权服务器扩展 rfc9101.ClientMetadataClaims # 客户端注册元数据扩展 rfc9101.AuthorizationServerMetadata # 服务器发现元数据扩展值得说明的是从 Authlib v1.7 起该模块全面基于joserfc实现见 docs/upgrades/jose.rstJWT 编解码与验签均来自joserfc.jwt相关异常如JoseError、DecodeError、UnsupportedAlgorithmError也统一从joserfc.errors抛出。三、核心扩展 JWTAuthenticationRequest注册与钩子机制3.1 通过 register_extension 接入授权服务器JWTAuthenticationRequest是一个扩展对象它依赖 Authlib 授权服务器的扩展注册机制。在 authorization_server.py 中register_extension会把扩展实例追加到服务器内部列表而扩展的__call__方法见 authorization_server.py会把parse_authorization_request注册到before_get_authorization_grant钩子上从而在授权端点处理授权请求之前先完成 Request Object 的解析与替换。一个最小可用的接入方式如下摘自 authorization_server.py 的类文档from authlib.oauth2 import rfc9101 class JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest): def resolve_client_public_key(self, client): return get_jwks_for_client(client) def get_request_object(self, request_uri: str): try: return requests.get(request_uri).text except requests.Exception: return None def get_server_metadata(self): return { issuer: ..., authorization_endpoint: ..., require_signed_request_object: ..., } def get_client_require_signed_request_object(self, client): return client.require_signed_request_object authorization_server.register_extension(JWTAuthenticationRequest())3.2 构造参数支持能力的开关JWTAuthenticationRequest.__init__接受两个布尔参数authorization_server.py参数默认值含义support_requestTrue是否支持request参数按值传递support_request_uriTrue是否支持request_uri参数按引用传递当客户端提交了服务器不支持的传递方式时分别抛出request_not_supported与request_uri_not_supported错误。3.3 类级 Claims 校验器类属性claims_validator定义了一个 JWT Claims 注册表authorization_server.pyclaims_validator jwt.JWTClaimsRegistry( client_id{essential: True}, )它声明client_idclaim 是必备essential的——Request Object 的 claims 中缺少client_id将直接校验失败。四、四个必须覆写的钩子方法JWTAuthenticationRequest是一个半成品基类它把与业务强相关的行为抽象为四个钩子方法其中两个必须实现两个带默认值可按需覆写。4.1resolve_client_public_key(client)解析客户端公钥必实现用于验证 Request Object 的 JWT 签名。客户端可能持有多个公钥此时可借助 JWT header 中的kid从 JWK Set 中选取对应密钥。官方示例authorization_server.pyfrom joserfc import KeySet class JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest): def resolve_client_public_key(self, client): if client.jwks_uri: data requests.get(client.jwks_uri).json() return KeySet.import_key_set(data) return KeySet.import_key_set(client.jwks)该方法返回joserfc的Key或KeySet对象随后在_decode_request_object中通过import_any_key(jwks)来自 authlib/_joserfc_helpers.py统一导入。4.2get_request_object(request_uri)按引用获取 Request Object启用 request_uri 时必实现仅在支持request_uri时需要实现。默认实现直接raise NotImplementedError()。官方示例authorization_server.pyclass JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest): def get_request_object(self, request_uri: str): try: return requests.get(request_uri).text except requests.Exception: return None注意当获取失败或返回空值时服务器抛出invalid_request_uri错误_get_raw_request_object中的逻辑见 authorization_server.py。4.3get_server_metadata()服务器元数据默认返回空字典返回服务器元数据字典用于读取require_signed_request_object与request_object_signing_alg_values_supported。默认返回{}见 authorization_server.pyclass JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest): def get_server_metadata(self): return { issuer: ..., authorization_endpoint: ..., require_signed_request_object: ..., request_object_signing_alg_values_supported: [RS256, ...], }当require_signed_request_object为True时所有客户端都必须使用 Request Object否则授权请求直接被拒绝。4.4get_client_require_signed_request_object(client)客户端强制开关默认 False返回客户端元数据中的require_signed_request_object值。未覆写时恒为Falseauthorization_server.pyclass JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest): def get_client_require_signed_request_object(self, client): return client.require_signed_request_object4.5 可选的get_request_object_signing_algorithms(client)签名算法策略该方法决定验签时允许的 JWTalg列表authorization_server.py其默认逻辑如下优先读取服务器元数据中的request_object_signing_alg_values_supported若未配置则根据是否强制签名决定当客户端元数据或服务器元数据任一要求require_signed_request_object时只允许JWSRegistry.recommended推荐算法如 RS256 等否则在推荐算法基础上额外允许none不签名。开发者可显式覆写收紧算法白名单def get_request_object_signing_algorithms(self, client): return [RS256]这一默认行为从 v1.7 起生效默认只使用推荐 JWT 算法如需支持额外算法必须在get_server_metadata中显式声明参见 docs/upgrades/jose.rst。五、服务端校验流程从原始参数到安全负载整个解析流程由parse_authorization_request驱动authorization_server.py可拆解为以下步骤校验client_id存在且客户端存在缺少client_id抛invalid_clientMissing client_id parameter.查询不到客户端抛invalid_clientThe client does not exist on this server.决定是否走 Request Object 路径_shoud_proceed_with_request_objectrequest与request_uri同时出现→ 抛invalid_request二者互斥出现request但support_requestFalse→ 抛request_not_supported出现request_uri但support_request_uriFalse→ 抛request_uri_not_supported两者都未出现时检查客户端/服务器级require_signed_request_object强制开关被强制则抛invalid_request获取原始 Request Object_get_raw_request_object优先按request_uri拉取失败抛invalid_request_uri否则直接取request参数值解码与验签_decode_request_object用客户端公钥 算法白名单调用jwt.decode算法不在白名单 → 抛invalid_requestAuthorization requests must be signed with supported algorithms.解码失败 → 抛invalid_request_object其他 JOSE 错误 → 抛invalid_request_object携带原始错误描述Claims 一致性校验通过claims_validator校验 claimsclient_id必备URL 参数中的client_id与 Request Object 内的client_idclaim 必须完全一致否则抛invalid_requestRFC9101 明确要求二者相同Request Object 内部禁止再包含request或request_uri参数否则抛invalid_request替换请求负载将request_object.claims包装为BasicOAuth2Payload并赋给request.payload此后授权服务器后续的 grant 处理、consent 校验等都基于这份已验签的负载进行BasicOAuth2Payload定义见 authlib/oauth2/rfc6749/requests.py。六、强制启用机制两级 require_signed_request_object6.1 客户端元数据ClientMetadataClaimsregistration.py 中的ClientMetadataClaims扩展了 RFC7591 的客户端注册协议RFC7591 文档。它注册了一个额外 claimrequire_signed_request_object布尔类型缺省为False非布尔值抛InvalidClaimError。使用时需把它与rfc7591.ClientMetadataClaims一起注入客户端注册端点server.register_endpoint( ClientRegistrationEndpoint( claims_classes[ rfc7591.ClientMetadataClaims, rfc9101.ClientMetadataClaims, ] ) )在 tests/flask/test_oauth2/test_jwt_authorization_request.py 的test_registration中可以看到三种情形的断言缺省时返回require_signed_request_object: False显式传True时原样保留传非法字符串时返回invalid_client_metadata错误。6.2 服务器元数据AuthorizationServerMetadatadiscovery.py 中的AuthorizationServerMetadata扩展了 RFC8414 的服务器发现元数据RFC8414 文档同样只增加一个require_signed_request_object键并通过validate_boolean_value强制其为布尔值。它配合rfc8414.AuthorizationServerMetadata.validate的metadata_classes参数一起使用discovery.py底层实现见 authlib/oauth2/rfc8414/models.pyfrom authlib.oauth2 import rfc8414, rfc9101 metadata rfc8414.AuthorizationServerMetadata(data) metadata.validate(metadata_classes[rfc9101.AuthorizationServerMetadata])该用法同样适用于 OpenID Provider 元数据discovery.OpenIDProviderMetadata。对应测试见 tests/core/test_oauth2/test_rfc8414.py。6.3 两级开关的服务端行为差异在_shoud_proceed_with_request_object中authorization_server.py两级强制开关产生的错误消息不同客户端级触发Authorization requests for this client must use signed request objects.服务器级触发Authorization requests for this server must use signed request objects.且当任一强制开关打开时签名算法白名单自动排除none防止未签名的 Request Object 蒙混过关。七、错误码速查表Authlib 为 JAR 定义了 4 个专用 OAuth 2.0 错误见 errors.py全部继承自OAuth2ErrorHTTP 状态码均为 400错误类error值默认error_description触发场景InvalidRequestUriErrorinvalid_request_uriThe request_uri in the authorization request returns an error or contains invalid data.request_uri拉取失败或内容无效InvalidRequestObjectErrorinvalid_request_objectThe request parameter contains an invalid Request Object.request中的 JWT 解码/验签失败RequestNotSupportedErrorrequest_not_supportedThe authorization server does not support the use of the request parameter.服务器未启用support_requestRequestUriNotSupportedErrorrequest_uri_not_supportedThe authorization server does not support the use of the request_uri parameter.服务器未启用support_request_uri此外校验过程中还会复用rfc6749的通用错误InvalidClientErrorinvalid_client用于client_id缺失或客户端不存在与InvalidRequestErrorinvalid_request用于参数互斥、client_id 不一致、Request Object 内嵌request/request_uri、签名算法不支持、强制开关未满足等。八、端到端实战结合测试用例验证完整链路仓库中的 tests/flask/test_oauth2/test_jwt_authorization_request.py 是一份完整的可运行参考实现覆盖了 JAR 的全部关键路径。下面提炼其核心模式。8.1 测试中的扩展实现测试代码通过工厂函数register_request_object_extension注册扩展test_jwt_authorization_request.pyclass JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest): def resolve_client_public_key(self, client): return read_file_path(jwk_public.json) def get_request_object(self, request_uri: str): return request_object def get_server_metadata(self): return metadata or {} def get_client_require_signed_request_object(self, client): return client.client_metadata.get(require_signed_request_object, False) server.register_extension( JWTAuthenticationRequest( support_requestsupport_request, support_request_urisupport_request_uri ) )测试客户端通过jwks元数据注册公钥test_jwt_authorization_request.pyRequest Object 由joserfc.jwt.encode配合私钥生成。8.2 客户端生成 Request Object按值传递payload {response_type: code, client_id: client-id} request_obj jwt.encode( {alg: RS256}, payload, jwk.import_key(read_file_path(jwk_private.json)) ) url add_params_to_uri( authorize_url, {client_id: client-id, request: request_obj} ) rv test_client.get(url) assert rv.data bok8.3 按引用传递request_uriregister_request_object_extension(server, request_objectrequest_obj) url add_params_to_uri( authorize_url, {client_id: client-id, request_uri: https://client.test/request_object}, ) rv test_client.get(url) assert rv.data bok8.4 主要负面用例一览测试函数验证点预期错误test_request_and_request_uri_parametersrequest与request_uri互斥invalid_requesttest_server_require_request_object服务器级强制未用 Request Objectinvalid_requesttest_server_require_request_object_alg_none强制签名时algnone被拒invalid_requesttest_client_require_signed_request_object客户端级强制未用 Request Objectinvalid_requesttest_unsupported_request_parameter禁用request参数request_not_supportedtest_unsupported_request_uri_parameter禁用request_uri参数request_uri_not_supportedtest_invalid_request_uri_parameterrequest_uri拉取失败invalid_request_uritest_invalid_request_objectJWT 无效invalid_request_objecttest_missing_client_id/test_invalid_client_idclient_id缺失 / 客户端不存在invalid_clienttest_different_client_idURL 与 Request Object 的client_id不一致invalid_requesttest_request_param_in_request_objectRequest Object 内嵌request_uriinvalid_request这些用例同时验证了require_signed_request_object开启后algnone被自动排除的行为——即使客户端显式用none算法编码服务器也会以Authorization requests must be signed with supported algorithms.拒绝见 authorization_server.py。九、小结与实践建议按需启用JAR 是可选项。只有当客户端元数据或服务器元数据的require_signed_request_object为True时授权请求才必须携带 Request Object否则普通 GET/POST 参数仍然可用test_neither_request_nor_request_uri_parameter验证了这一点。强制签名时收紧算法一旦开启强制开关Authlib 默认只允许JWSRegistry.recommended算法并排除none若需自定义算法白名单覆写get_request_object_signing_algorithms或在get_server_metadata中声明request_object_signing_alg_values_supported。保持 client_id 一致URL 参数与 Request Object claims 中的client_id必须完全相同这是防混淆的关键校验。参考实现路径完整测试与集成示例位于 tests/flask/test_oauth2/test_jwt_authorization_request.py核心扩展源码位于 authlib/oauth2/rfc9101/authorization_server.py元数据扩展分别在 registration.py 与 discovery.py 中。如需与其他规范联动可继续查阅 RFC7591 客户端注册 与 RFC8414 服务器发现 文档。赞分享【免费下载链接】authlibThe ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS, JWE, JWK, JWA, JWT included.项目地址https://gitcode.com/gh_mirrors/au/authlib点击查看免费下载相关推荐Omi Desktop Insight 助手对齐审计Mac 两阶段深度提取管线与 Windows 落地实现Omi Desktop Insight 助手对齐审计Mac 两阶段深度提取管线与 Windows 落地实现 导读 本篇技术文章以仓库中 Ground TrutMoya 自定义授权插件AuthPlugin实战为请求注入 JWT 与访问令牌的完整指南Moya 自定义授权插件AuthPlugin实战为请求注入 JWT 与访问令牌的完整指南 导读网络请求通常需要携带 JWTJSON Web Token开发工具Electric Proxy Auth 实战为 Shape 同步请求实现服务端授权的完整剖析Electric Proxy Auth 实战为 Shape 同步请求实现服务端授权的完整剖析 本文基于 Electric 官方仓库中的 proxy auth后端数据同步数据库人工智能AI AgentMCP 服务上一篇如何在Windows和Linux上完美运行Switch游戏Ryujinx模拟器终极指南下一篇OCR表格提取3步解放扫描PDF数据让Excel录入效率提升90%创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考