☰
Authlib 之 Flask 资源服务器保护指南:require_oauth 装饰器、多 Scope 与 Token 校验实战
2026/10/13 9:13:35 网站建设 项目流程

【免费下载链接】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 官方文档 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)

该端点的有效条件是:

  1. 令牌同时包含profile和email两个 scope;
  2. 或者令牌包含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

四个步骤分别是:

  1. 解析 Authorization 头:parse_request_authorization()读取Authorization: Bearer <token>,按空格拆分为令牌类型与令牌字符串;无头则抛MissingAuthorizationError(401),类型不受支持则抛UnsupportedTokenTypeError(见 authlib/oauth2/rfc6749/resource_protector.py);
  2. 校验请求本身:validate_request()默认空实现,可覆写以实现「要求特定请求头」等额外约束;
  3. 认证令牌:调用你实现的authenticate_token()查询令牌对象;
  4. 校验令牌:检查过期、吊销与 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 字段
请求头缺失 Authorization401missing_authorization
Authorization 类型不受支持(如invalid token)401unsupported_token_type
令牌不存在 / 已过期 / 已吊销401invalid_token
令牌有效但 scope 不足403insufficient_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.

项目地址:https://gitcode.com/gh_mirrors/au/authlib
点击查看免费下载
上一篇:Certbot 跨平台兼容层解析:certbot.compat.os 模块的安全文件操作设计
下一篇:Ray Core 任务全生命周期深度剖析:从 `@ray.remote` 定义到 `ray.get` 返回

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

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

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

立即咨询