【免费下载链接】authlib
The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS, JWE, JWK, JWA, JWT included.
本文围绕 Authlib 官方文档 docs/oauth2/resource-server/flask.rst 展开,系统讲解如何在 Flask 应用中将 API 端点保护为 OAuth 2.0 资源服务器:通过ResourceProtector与require_oauth装饰器校验访问令牌、以 AND/OR 组合多种 scope、用optional参数开放匿名访问,以及借助current_token代理访问已认证令牌。读完本文,你将能够在自己的 Flask 项目中独立搭建一个基于 Bearer Token 的资源保护层,并理解其底层校验链路的完整实现。
资源服务器与 Flask 集成概览
在 OAuth 2.0 的授权框架中,**资源服务器(Resource Server)**是承载用户受保护数据、负责验证访问令牌的 API 服务。它只做一件事:校验授权服务器签发的令牌,只允许携带合法且具备相应 scope 的令牌的客户端访问受保护资源(见 docs/oauth2/resource-server/index.rst)。
资源服务器可以与授权服务器完全分离——它甚至不持有用户数据库,只需通过 RFC 7662 Token Introspection 接口向授权服务器询问令牌状态(详见 authlib/oauth2/rfc7662/token_validator.py)。
在 Authlib 中,Flask 集成入口位于 authlib/integrations/flask_oauth2/init.py,提供了:
ResourceProtector:创建require_oauth装饰器的核心类;current_token:指向当前已认证令牌的 WerkzeugLocalProxy;token_authenticated等信号,可在令牌认证成功后执行自定义逻辑。
第一步:定义 BearerTokenValidator
要保护资源,首先要为令牌校验器实现「如何根据令牌字符串查回令牌对象」。文档给出的最小示例(docs/oauth2/resource-server/flask.rst)如下:
from flask import jsonify from authlib.integrations.flask_oauth2 import ResourceProtector, current_token from authlib.oauth2.rfc6750 import BearerTokenValidator class MyBearerTokenValidator(BearerTokenValidator): def authenticate_token(self, token_string): return Token.query.filter_by(access_token=token_string).first() require_oauth = ResourceProtector() # 目前仅支持 bearer token require_oauth.register_token_validator(MyBearerTokenValidator())底层校验逻辑:authenticate_token 与 validate_token
BearerTokenValidator定义在 authlib/oauth2/rfc6750/validator.py,其中TOKEN_TYPE = "bearer",并声明了两个必须由开发者实现/依赖的方法:
authenticate_token(token_string):从数据库查询令牌对象。基类抛出NotImplementedError,必须由开发者覆写;validate_token(token, scopes, request):基类已给出默认实现——依次检查令牌是否存在、is_expired()、is_revoked(),最后调用scope_insufficient()做 scope 匹配,不通过时分别抛出InvalidTokenError(401)或InsufficientScopeError(403),错误类定义见 authlib/oauth2/rfc6750/errors.py。
因此,你的Token模型需要具备is_expired()、is_revoked()与get_scope()能力——这正是 authlib/oauth2/rfc6749/models.py 中TokenMixin要求实现的三个核心方法。
让配置更省事:create_bearer_token_validator
如果你使用 SQLAlchemy,不必手写 validator。Authlib 在 authlib/integrations/sqla_oauth2/functions.py 提供了工厂函数:
from authlib.integrations.sqla_oauth2 import create_bearer_token_validator from .models import Token BearerTokenValidator = create_bearer_token_validator(db.session, Token) require_oauth.register_token_validator(BearerTokenValidator())它生成的_BearerTokenValidator内部已实现authenticate_token,通过session.query(token_model).filter_by(access_token=token_string)查询令牌(见 authlib/integrations/sqla_oauth2/functions.py)。这一用法在仓库测试 tests/flask/test_oauth2/test_oauth2_server.py 中得到直接印证。
无法访问数据库?使用令牌内省(Token Introspection)
当资源服务器无法访问Token模型(数据库),而授权服务器提供了内省端点时,改用IntrospectTokenValidator(参考文档中 :ref:require_oauth_introspection指引)。它定义于 authlib/oauth2/rfc7662/token_validator.py,需要你实现introspect_token(token_string),向授权服务器的内省端点发起请求并返回 JSON:
from authlib.oauth2.rfc7662 import IntrospectTokenValidator class MyIntrospectTokenValidator(IntrospectTokenValidator): def introspect_token(self, token_string): url = "https://example.com/oauth/introspect" resp = requests.post(url, data={"token": token_string}) resp.raise_for_status() return resp.json() require_oauth.register_token_validator(MyIntrospectTokenValidator())校验时它检查token["active"]是否为真,并同样执行 scope 充分性判断——令牌无效返回 401invalid_token,scope 不足返回 403insufficient_scope。相关 RFC 7662 规范说明见 docs/oauth2/specs/rfc7662。
第二步:用 require_oauth 保护你的 API 端点
完成 validator 注册后,就可以用装饰器保护端点。文档给出的核心示例(docs/oauth2/resource-server/flask.rst):
@app.route('/user') @require_oauth('profile') def user_profile(): # 如果 Token 模型带有 `.user` 外键 user = current_token.user return jsonify(user)不带 scope 的端点
如果某资源不需要任何 scope 保护,直接省略参数即可(docs/oauth2/resource-server/flask.rst):
@app.route('/user') @require_oauth def user_profile(): user = current_token.user return jsonify(user)注意@require_oauth与@require_oauth()两种写法都受支持——在 authlib/integrations/flask_oauth2/resource_protector.py 的实现中,__call__会判断scopes是否可调用:若require_oauth直接被用作装饰器(可调用对象是视图函数),则直接返回装饰器;否则返回闭包装饰器。仓库测试 tests/flask/test_oauth2/test_oauth2_server.py 同时验证了/info(带括号)与/no-parens(不带括号)两种路由均可正常访问。
current_token:一个指向 Token 模型的代理
current_token是 Werkzeug 的LocalProxy,指向 Flask 应用上下文g中由acquire_token()保存的令牌对象(见 authlib/integrations/flask_oauth2/resource_protector.py):
def _get_current_token(): return g.get("authlib_server_oauth2_token") current_token = LocalProxy(_get_current_token)由于令牌对象上存在user外键关系,可直接通过current_token.user访问用户。令牌认证成功后还会发送token_authenticated信号(信号定义见 authlib/integrations/flask_oauth2/signals.py),可用于埋点或审计。
偏爱 with 语句?使用 require_oauth.acquire
如果你不喜欢装饰器风格,文档提供了等价的with语句用法(docs/oauth2/resource-server/flask.rst):
@app.route('/user') def user_profile(): with require_oauth.acquire('profile') as token: user = token.user return jsonify(user)其实现见 authlib/integrations/flask_oauth2/resource_protector.py:acquire()是@contextmanager,内部yield self.acquire_token(scopes),并捕获OAuth2Error转为 HTTP 异常响应。仓库测试 tests/flask/test_oauth2/test_oauth2_server.py 验证了/acquire路由的完整可用性。
多 Scope 组合:AND、OR 与混合模式
自 v1.0 起(.. versionchanged:: v1.0),同一端点可以应用多个 scope,并支持AND、OR与混合三种逻辑(docs/oauth2/resource-server/flask.rst)。
AND 模式:一个字符串内的多个 scope
@app.route('/profile') @require_oauth(['profile email']) def user_profile(): user = current_token.user return jsonify(user)要求令牌同时包含profile和email两个 scope。
OR 模式:列表中的多个 scope
@app.route('/profile') @require_oauth(['profile', 'email']) def user_profile(): user = current_token.user return jsonify(user)要求令牌包含profile或email任一 scope 即可。
混合模式:AND 与 OR 的组合
@app.route('/profile') @require_oauth(['profile email', 'user']) def user_profile(): user = current_token.user return jsonify(user)该端点的有效条件是:
- 令牌同时包含
profile和email两个 scope; - 或者令牌包含
userscope。
底层判定逻辑:scope_insufficient
这一语义由基类TokenValidator.scope_insufficient()实现(authlib/oauth2/rfc6749/resource_protector.py):
@staticmethod def scope_insufficient(token_scopes, required_scopes): if not required_scopes: return False token_scopes = scope_to_list(token_scopes) if not token_scopes: return True token_scopes = set(token_scopes) for scope in required_scopes: resource_scopes = set(scope_to_list(scope)) if token_scopes.issuperset(resource_scopes): return False return True逻辑要点:
- 列表中的每一项(如
"profile email"、"user")是一个独立候选条件,任一条件满足即通过(OR 语义); - 每一项内部按空格拆分为多个 scope,要求令牌 scope 集合是它的超集(AND 语义,使用
issuperset); - 无
required_scopes时不校验;令牌无任何 scope 时直接判定不足。
字符串与列表的互相转换由 authlib/oauth2/rfc6749/util.py 的scope_to_list()/list_to_scope()完成。仓库测试 tests/flask/test_oauth2/test_oauth2_server.py 验证了 AND 模式返回 403insufficient_scope、OR 模式返回 200 的行为。
可选认证:optional=True 服务公开端点
require_oauth还有一个用于服务公开端点的参数(docs/oauth2/resource-server/flask.rst):
@app.route('/timeline') @require_oauth(optional=True) def timeline_api(): if current_token: return get_user_timeline(current_token.user) return get_public_timeline()在 authlib/integrations/flask_oauth2/resource_protector.py 的实现中,optional=True只在捕获到MissingAuthorizationError(请求未携带任何 Authorization 头)时直接放行;若携带了令牌但校验失败(无效、过期或 scope 不足),依然返回错误响应,绝不会让一个无效令牌悄悄通过。测试 tests/flask/test_oauth2/test_oauth2_server.py 验证:无令牌访问返回匿名数据,携带合法令牌则返回对应用户数据。
在 MethodView 与 Flask-Restful 中使用
装饰器同样适用于flask.views.MethodView与flask_restful.Resource(docs/oauth2/resource-server/flask.rst):
from flask.views import MethodView class UserAPI(MethodView): decorators = [require_oauth('profile')] from flask_restful import Resource class UserAPI(Resource): method_decorators = [require_oauth('profile')]两者的原理都是通过类属性decorators/method_decorators将装饰器应用到类内所有方法上,确保每个 HTTP 方法(GET/POST/PUT/DELETE)在进入视图函数前先完成令牌与 scope 校验。
完整校验链路与请求解析
ResourceProtector的请求校验在 authlib/oauth2/rfc6749/resource_protector.py 中串联执行:
def validate_request(self, scopes, request, **kwargs): validator, token_string = self.parse_request_authorization(request) validator.validate_request(request) token = validator.authenticate_token(token_string) validator.validate_token(token, scopes, request, **kwargs) return token四个步骤分别是:
- 解析 Authorization 头:
parse_request_authorization()读取Authorization: Bearer <token>,按空格拆分为令牌类型与令牌字符串;无头则抛MissingAuthorizationError(401),类型不受支持则抛UnsupportedTokenTypeError(见 authlib/oauth2/rfc6749/resource_protector.py); - 校验请求本身:
validate_request()默认空实现,可覆写以实现「要求特定请求头」等额外约束; - 认证令牌:调用你实现的
authenticate_token()查询令牌对象; - 校验令牌:检查过期、吊销与 scope 充分性。
Flask 集成层通过 authlib/integrations/flask_oauth2/requests.py 的FlaskJsonRequest将 Flask 请求包装为框架无关的JsonRequest,再交给上述链路处理(authlib/integrations/flask_oauth2/resource_protector.py)。出错时由raise_error_response()将OAuth2Error转换为带 JSON 响应体与WWW-Authenticate: Bearer realm=..., error="invalid_token", error_description="..."头的 HTTP 异常(authlib/integrations/flask_oauth2/errors.py 与 authlib/oauth2/rfc6750/errors.py)。
错误响应速查
基于 authlib/oauth2/rfc6750/errors.py 与仓库测试 tests/flask/test_oauth2/test_oauth2_server.py 的验证,各失败场景的响应如下:
| 场景 | HTTP 状态码 | error 字段 |
|---|---|---|
| 请求头缺失 Authorization | 401 | missing_authorization |
Authorization 类型不受支持(如invalid token) | 401 | unsupported_token_type |
| 令牌不存在 / 已过期 / 已吊销 | 401 | invalid_token |
| 令牌有效但 scope 不足 | 403 | insufficient_scope |
小结
借助 Authlib 的 Flask 集成,你可以用极少的样板代码为 API 构建完整的 Bearer Token 资源保护层:定义BearerTokenValidator(或使用create_bearer_token_validator/IntrospectTokenValidator)→ 注册到ResourceProtector→ 用require_oauth装饰器(或acquirewith 语句)保护端点。多 scope 的 AND/OR/混合语义、optional=True公开端点、current_token代理与 MethodView 集成,覆盖了资源服务器开发的绝大多数实战场景,且每一项都有 tests/flask/test_oauth2/test_oauth2_server.py 中的测试用例作为行为佐证。若需与 Django 集成对比,可参考同目录的 docs/oauth2/resource-server/django.rst。
【免费下载链接】authlib
The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS, JWE, JWK, JWA, JWT included.
相关推荐
Everywhere 安装部署指南:在 Windows 上快速用上屏幕感知 AI 助手
Everywhere 安装部署指南:在 Windows 上快速用上屏幕感知 AI 助手 Everywhere 是一款屏幕感知型的桌面 AI 助手:它能直接读懂你
Authlib Django 资源服务器集成:使用 require_oauth 保护你的 API 端点
Authlib Django 资源服务器集成:使用 require_oauth 保护你的 API 端点 本文聚焦 Authlib 中 Django 资源服务器(
Authlib 实战:用 Flask 构建 OAuth 1.0 资源服务器与 ResourceProtector 权限保护
Authlib 实战:用 Flask 构建 OAuth 1.0 资源服务器与 ResourceProtector 权限保护 导读 在 OAuth 1.0 协议体
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考