☰
Authlib 实战:RFC 7523 JWT Bearer 指南——用 JWT 实现 OAuth 2.0 客户端认证与授权 Grant
2026/10/13 2:46:11 网站建设 项目流程

【免费下载链接】authlib

The 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 对 RFC 7523(JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants)的完整实现指南。RFC 7523 定义了两种能力:一是把 JWT 作为 OAuth 2.0 授权 Grant(assertion)直接换取访问令牌;二是把 JWT 作为客户端认证方式(client_secret_jwt/private_key_jwt)替代传统的 client secret 直传。读完本文,你将掌握在 Authlib 授权服务器上注册JWTBearerGrant、扩展客户端认证方法,以及在 OAuth2Session 客户端上签发 JWT 断言并获取令牌的完整实战方案,并理解其底层校验流程与测试验证方式。

一、RFC 7523 概览:它解决什么问题

RFC 7523 全称JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants,它把 RFC 7519 的 JWT 与 OAuth 2.0 体系打通,主要解决两类场景:

  1. JWT 作为授权 Grant:客户端直接向令牌端点提交一个 JWT(assertion参数),授权服务器验签并解析 claims 后签发访问令牌。典型应用是服务器到服务器(service-to-service)的凭证交换,例如 Google 服务账号模式。
  2. JWT 作为客户端认证:客户端用client_secret(HMAC)或自己的私钥(RSA/EC)对一段 JWT 签名,通过client_assertion参数向令牌端点证明身份,对应 OpenID Connect 中定义的client_secret_jwt与private_key_jwt两种方法。

在 Authlib 中,该规范由 authlib/oauth2/rfc7523/ 模块实现,对外导出以下核心 API(见 模块__all__):

  • 服务端授权 Grant:JWTBearerGrant
  • 服务端客户端认证:JWTBearerClientAssertion
  • 客户端签名辅助:client_secret_jwt_sign、private_key_jwt_sign、ClientSecretJWT、PrivateKeyJWT
  • 令牌相关:JWTBearerToken、JWTBearerTokenGenerator、JWTBearerTokenValidator

二、使用 JWT 作为授权 Grant(JWTBearerGrant)

2.1 基类与 Grant 类型标识

JWTBearerGrant定义在 jwt_bearer.py,继承自 RFC 6749 的BaseGrant与TokenEndpointMixin,因此它的注册与使用方式与 RFC 6749 内建 Grant 完全一致——可以通过AuthorizationServer.register_grant注册(对应 authorization_server.py)。

它的 Grant 类型标识为:

JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"

即客户端在令牌端点提交grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer时,命中该 Grant。

2.2 完整示例:实现 JWTBearerGrant 子类

原文档给出的基类模板要求开发者必须实现若干缺失方法。以下代码来自 RFC 7523 文档 的示例,并结合源码补全注释:

from joserfc.jwk import KeySet from authlib.oauth2.rfc7523 import JWTBearerGrant as _JWTBearerGrant class JWTBearerGrant(_JWTBearerGrant): def get_audiences(self): # Per RFC 7523 Section 3, both the token endpoint URL and the # authorization server's issuer identifier are valid audience values. return ['https://example.com/oauth/token', 'https://example.com'] def resolve_issuer_client(self, issuer): # 使用 client_id 作为 issuer 时,直接按 issuer 查客户端 return Client.objects.get(client_id=issuer) def resolve_client_public_key(self, client): # 若客户端表有 jwks 列,把 JWK Set 解析为密钥集合用于验签 key_set = KeySet.import_key_set(client.jwks) return key_set def authenticate_user(self, subject): # 断言中包含 sub 时,用 sub 值定位用户(例如 sub 是 email) return User.objects.get(email=subject) def has_granted_permission(self, client, user): # 校验客户端是否拥有访问用户资源的授权 # 例如存在 UserGrant 表记录用户对客户端的授权 grant = UserGrant.objects.get(client_id=client.client_id, user_id=user.id) if grant: return grant.enabled return False # 注册到授权服务器 authorization_server.register_grant(JWTBearerGrant)

创建客户端时,授权服务器会生成若干密钥对,服务器自身只需保留公钥(或 JWK Set),用于解码断言值;私钥由客户端妥善保管用于签名。

2.3 必须实现的抽象方法逐一解析

从 jwt_bearer.py 的源码可以看到,以下方法在基类中抛出NotImplementedError(),子类必须覆写:

方法职责返回说明
resolve_issuer_client(issuer)根据断言中的iss查找客户端返回 Client 实例;返回None时授权服务器抛出invalid_grant("The client does not exist on this server.")
resolve_client_public_key(client)解析客户端公钥用于解码断言返回OctKey、RSAKey、ECKey、OKPKey或KeySet实例
authenticate_user(subject)根据断言中的sub定位用户返回 User 实例;返回空值时抛出invalid_grant("Invalid 'sub' value in assertion")
has_granted_permission(client, user)校验客户端是否有权访问该用户资源返回布尔值
get_audiences()返回本授权服务器接受的有效aud列表列表为空时跳过 aud 值校验(仅检查存在性),见 2.4 节

需要特别注意的是,在 v1.0.0 中这些未实现方法发生过一次变更(versionchanged:: v1.0.0),因此以当前仓库源码中的方法签名为准;此外 v1.8 起已用resolve_client_public_key取代早期的resolve_client_key(旧方法仍在兼容期,会输出 deprecation 警告,见 process_assertion_claims)。

2.4 断言校验流程与安全参数(CLAIMS_OPTIONS / LEEWAY)

基类内置了一组默认的 claims 校验选项与时钟偏移容差(见 jwt_bearer.py):

CLAIMS_OPTIONS = { "iss": {"essential": True}, "aud": {"essential": True}, "exp": {"essential": True}, } LEEWAY = 60 # 允许的时钟偏移秒数,默认 60 秒

verify_claims(jwt_bearer.py)会把这些选项与get_audiences()合并成jwt.JWTClaimsRegistry并执行校验:

  • iss、aud、exp默认均为必需(essential);
  • 若get_audiences()返回非空列表,aud会升级为{"essential": True, "values": audiences},即断言中的aud必须命中列表中的某个值——这正是 RFC 7523 Section 3 要求的"授权服务器必须拒绝任何不以其自身为受众的 JWT";
  • 若返回空列表,v1.8 起会输出 deprecation 警告,提示受众校验未来将成为强制项;
  • 任何JoseError都会被转换为 OAuth 2.0 的invalid_grant错误响应。

整个请求处理流程(validate_token_request,见 jwt_bearer.py)大致为:

  1. 从表单取assertion参数,缺失则抛invalid_request;
  2. extract_assertion用jws.extract_compact拆解 JWT 并解析 payload(要求 payload 必须是 JSON 对象);
  3. 读取iss(此时尚未验签,不能假定其存在或为字符串),据此调用resolve_issuer_client定位客户端;
  4. 用resolve_client_public_key得到的密钥执行jwt.decode验签;
  5. verify_claims校验iss/aud/exp等 claims;
  6. 校验client.check_grant_type(self.GRANT_TYPE),未授权则抛unauthorized_client;
  7. 若断言含sub,调用authenticate_user定位用户,并调用has_granted_permission做权限检查;
  8. 校验请求的 scope。

请求参数格式遵循 RFC 7523 Section 2.1,示例 HTTP 请求如下(源码 docstring 中的原始示例):

POST /token.oauth2 HTTP/1.1 Host: as.example.com Content-Type: application/x-www-form-urlencoded grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer &assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0. eyJpc3Mi[...omitted for brevity...]. J9l-ZhwP[...omitted for brevity...]

校验通过后,create_token_response(jwt_bearer.py)会生成令牌并返回200响应,默认include_refresh_token=False(JWT 断言模式不签发刷新令牌)。

2.5 扩展:将访问令牌也输出为 JWT 格式

作为锦上添花的进阶方案,JWTBearerTokenGenerator(token.py)可以把 jwt-bearer grant 签发的访问令牌直接生成为 JWT 格式,并注册到授权服务器:

authorization_server.register_token_generator( "urn:ietf:params:oauth:grant-type:jwt-bearer", JWTBearerTokenGenerator(private_rsa_key), )

其构造参数包括:

  • secret_key:私钥(bytes / JWK / JWK Set);
  • issuer:可选的发行者标识,会写入issclaim;
  • alg:JWT 签名算法,默认RS256。

生成的令牌 payload 包含scope、grant_type、iat、exp、client_id,可选的iss与sub(通过覆写get_sub_value定制 sub 取值)。由于 JWT 令牌短期有效,可以不落库,此时可让子类重写save_token为空操作:

class MyJWTBearerGrant(JWTBearerGrant): def save_token(self, token): pass

对应地,资源服务器侧可用JWTBearerTokenValidator(validator.py)验证这类 JWT 格式的 Bearer 令牌,其构造参数为public_key、可选issuer、realm、leeway=60,默认强制校验exp、client_id、grant_type三个 claims。

三、使用 JWT 作为客户端认证

3.1 两种认证方法的由来

在 RFC 6749 中,Authlib 提供了三种内建客户端认证方法:none、client_secret_post、client_secret_basic。借助 Assertion Framework,可以扩展更多认证方法。本节新增两种:client_secret_jwt与private_key_jwt。

注意:RFC 7523 本身并未定义这两个名字,它们来自 OpenID Connect 规范的 ClientAuthentication 章节。在 Authlib 中,二者共享同一套实现逻辑——都是把 JWT 作为client_assertion提交,区别仅在签名密钥:用客户端client_secret做 HMAC 签名的叫client_secret_jwt;用客户端私钥(RSA/EC 等)签名的叫private_key_jwt。

3.2 服务端实现:JWTBearerClientAssertion

AuthorizationServer提供了register_client_auth_method(见 authorization_server.py)用于扩展客户端认证方法。服务端自定义认证逻辑时,直接继承JWTBearerClientAssertion:

class JWTClientAuth(JWTBearerClientAssertion): def get_audiences(self): # Per RFC 7523 Section 3, both the token endpoint URL and the # authorization server's issuer identifier are valid audience values. return ['https://example.com/oauth/token', 'https://example.com'] def validate_jti(self, claims, jti): # validate_jti 是 OpenID Connect 的强制要求,RFC 7523 中可选 # 用缓存校验 jti 是否被使用过,防止重放攻击 key = 'jti:{}-{}'.format(claims['sub'], jti) if cache.get(key): return False cache.set(key, 1, timeout=3600) return True def resolve_client_public_key(self, client, headers): if headers['alg'] == 'HS256': return client.client_secret if headers['alg'] == 'RS256': return client.public_key # 你可以在此支持其他 alg 值 authorization_server.register_client_auth_method( JWTClientAuth.CLIENT_AUTH_METHOD, JWTClientAuth() )

关键常量与逻辑(见 client.py):

CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" CLIENT_AUTH_METHOD = "client_assertion_jwt"
  • get_audiences返回客户端断言 JWT 中可接受的aud值列表。按 RFC 7523 Section 3,令牌端点 URL 与授权服务器 issuer 标识都是合法受众;
  • validate_jti负责jti去重用(构造JWTClientAuth(validate_jti=False)可关闭强制校验,见 client.py);
  • resolve_client_public_key根据 JWT header 中的alg决定用哪种密钥验签。

verify_claims(client.py)对客户端断言强制校验:iss、sub、aud、exp均为必需,且iss与sub必须相等并等于 client_id;开启 jti 校验时jti缺失或重复都会导致invalid_client。认证成功后,authenticate_client还会检查该客户端是否被允许使用client_assertion_jwt方法访问令牌端点(client.check_endpoint_auth_method)。

关于CLIENT_AUTH_METHOD的取值:你可能注意到它的值是client_assertion_jwt,而不是client_secret_jwt或private_key_jwt——因为二者的校验逻辑完全相同,client_secret_jwt与private_key_jwt的区别只体现在resolve_client_public_key中对alg的分支处理上:

  • alg为 MAC 类(如HS256)→ 用客户端client_secret作为验签密钥,即client_secret_jwt;
  • alg为RS256等非对称算法 → 用客户端公钥验签,即private_key_jwt。

如果只在resolve_client_public_key中接受HS256,那么可以显式把类属性改为CLIENT_AUTH_METHOD = 'client_secret_jwt'以表达更精确的语义。

让 grant 支持该认证方法:注册客户端认证方法后,还需把它加入目标 Grant 的TOKEN_ENDPOINT_AUTH_METHODS白名单,例如授权码 Grant:

from authlib.oauth2.rfc6749 import grants class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): TOKEN_ENDPOINT_AUTH_METHODS = [ 'client_secret_basic', JWTClientAuth.CLIENT_AUTH_METHOD, ] # ...

这一调用链在 tests/flask/test_oauth2/test_jwt_bearer_client_auth.py 中有完整验证:测试里把JWTBearerClientAssertion.CLIENT_AUTH_METHOD加入ClientCredentialsGrant的TOKEN_ENDPOINT_AUTH_METHODS,并通过server.register_client_auth_method注册后,用client_assertion_type/client_assertion表单参数发起令牌请求,覆盖了缺失断言、签名错误、jti 重放等异常分支。

3.3 客户端实现:在 OAuth2Session 中注册 JWT 认证

Authlib 的 RFC 7523 实现为 OAuth 2 Session 提供client_secret_jwt与private_key_jwt两种客户端认证方法。

注册client_secret_jwt:

from authlib.oauth2.rfc7523 import ClientSecretJWT from authlib.integrations.requests_client import OAuth2Session session = OAuth2Session( 'your-client-id', 'your-client-secret', token_endpoint_auth_method='client_secret_jwt' ) token_endpoint = 'https://example.com/oauth/token' session.register_client_auth_method(ClientSecretJWT(token_endpoint)) session.fetch_token(token_endpoint)

注册private_key_jwt:

from authlib.oauth2.rfc7523 import PrivateKeyJWT with open('your-private-key.pem', 'rb') as f: private_key = f.read() session = OAuth2Session( 'your-client-id', private_key, token_endpoint_auth_method='private_key_jwt' # NOTICE HERE ) token_endpoint = 'https://example.com/oauth/token' session.register_client_auth_method(PrivateKeyJWT(token_endpoint)) session.fetch_token(token_endpoint)

两个类的可配置项(见 auth.py):

类name默认 alg签名密钥来源
ClientSecretJWTclient_secret_jwtHS256客户端的client_secret(自动导入为OctKey)
PrivateKeyJWTprivate_key_jwtRS256客户端私钥(支持RSAKey/ECKey/OKPKey,否则自动按 RSA 导入)

两者构造参数均为token_endpoint、claims(额外 JWT claims)、headers(额外 JWT header,如kid、jku)、alg。register_client_auth_method以auth.name为键注册(见 client.py),令牌请求时按token_endpoint_auth_method找到对应认证器,把client_assertion_type与client_assertion两个参数追加到表单体中(见 auth.py)。

底层签名函数client_secret_jwt_sign/private_key_jwt_sign(assertion.py)生成的 JWT 断言遵循以下约定:

  • iss与sub均为client_id;
  • aud为令牌端点 URL(构造时传入的token_endpoint,未传则取当前请求 URI);
  • exp默认为iat + 3600秒(可通过expires_in调整);
  • 自动生成 36 位随机jti(也可在claims中显式指定),用于防重放。

这些行为在 tests/core/test_oauth2/test_rfc7523_client_secret.py 与 tests/core/test_oauth2/test_rfc7523_private_key.py 中有详尽断言,包括默认参数、自定义alg、自定义 claims/headers、自定义 jti 等场景;tests/clients/test_requests/test_oauth2_session.py 则验证了在OAuth2Session中分别以字符串密钥与 JWK 对象两种形式使用ClientSecretJWT/PrivateKeyJWT发起fetch_token的完整链路。

更简单的断言客户端:AssertionSession

如果目标是"JWT 作为授权 Grant"的客户端(而非客户端认证),可以直接使用现成的断言会话:requests体系对应authlib.integrations.requests_client.AssertionSession,httpx体系对应authlib.integrations.httpx_client.AssertionSession(及异步版AsyncAssertionSession)。它们内部通过JWTBearerGrant.sign静态方法(jwt_bearer.py)自动生成 JWT 断言并请求令牌,支持token_endpoint、issuer、subject、audience、claims、token_placement、scope等参数,见 assertion_session.py 与 httpx 版 assertion_client.py。

四、API 参考摘要

authlib.oauth2.rfc7523对外公开的 API 汇总(对应 模块__all__):

  • JWTBearerGrant:JWT 授权 Grant 基类,覆写resolve_issuer_client、resolve_client_public_key、authenticate_user、has_granted_permission、get_audiences后注册使用;
  • JWTBearerClientAssertion:JWT 客户端认证实现基类,覆写get_audiences、validate_jti、resolve_client_public_key后注册使用;
  • ClientSecretJWT/PrivateKeyJWT:OAuth2Session 客户端侧的 JWT 认证器(client_secret_jwt/private_key_jwt);
  • client_secret_jwt_sign/private_key_jwt_sign:底层断言签名函数;
  • JWTBearerToken/JWTBearerTokenValidator:JWT 格式 Bearer 令牌的资源服务器校验组件;
  • JWTBearerTokenGenerator:为 jwt-bearer grant 生成 JWT 格式访问令牌的生成器。

五、更多相关资源

  • RFC 6749 授权框架在 Authlib 中的完整说明见 RFC 6749 文档,JWTBearerGrant的注册、令牌生成、错误处理机制均复用了该框架;
  • 客户端断言会话与 OAuth2Session 的通用用法见 OAuth 2 Session 指南;
  • 服务端验证可参考 Flask 集成测试 test_jwt_bearer_client_auth.py 以及核心单元测试 test_rfc7523_client_secret.py、test_rfc7523_private_key.py;
  • 注意 v1.0.0 之后未实现方法签名有调整,v1.8 起resolve_client_public_key取代resolve_client_key、get_audiences空列表将弃用,升级时请以当前源码签名为准。

【免费下载链接】authlib

The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS, JWE, JWK, JWA, JWT included.

项目地址:https://gitcode.com/gh_mirrors/au/authlib
点击查看免费下载
上一篇:高效实现Blender与MMD互通:MMD Tools插件完整实战指南
下一篇:MAS 激活指南:4 种方式搞定 Windows 与 Office 激活

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询