在 Sanic 中实现 JWT 认证与授权:装饰器 + 蓝图实战指南
后端Web框架【免费下载链接】sanicAccelerate your web app development | Build fast. Run fast.项目地址https://gitcode.com/gh_mirrors/sa/sanic点击查看免费下载导读本文基于 Sanic 官方指南中的认证示例系统讲解如何在 Sanic 应用中通过JWTJSON Web Token 装饰器组合实现认证Authentication与授权Authorization控制。你将掌握三个文件的完整实现server.py应用入口与受保护路由、login.py登录蓝图签发 Token、auth.pyToken 校验装饰器并通过curl完成全流程验证。文中还会结合 Sanic 源码深入剖析request.token属性与Unauthorized/Forbidden内置异常的实现原理帮助你在自己的项目中落地一套可复用、可扩展的访问控制方案。原文出处guide/content/en/guide/how-to/authentication.md。文中使用的装饰器模式取自 Sanic 最佳实践文档 Decorators。一、方案概览认证与授权如何落地认证Authentication回答的是你是谁授权Authorization回答的是你能做什么。本文示例聚焦前者但设计上两者可以无缝衔接。该方案选用JWT作为凭证载体其核心思路如下客户端向登录接口提交身份信息示例中简化处理直接签发 Token服务端使用密钥SECRET签发 JWT 并返回给客户端客户端在后续请求的Authorization请求头中以Bearer token形式携带 Token受保护路由通过protected装饰器校验 Token合法则放行处理函数非法则返回401 Unauthorized。适用性说明虽然示例使用 JWT但文档明确指出——这些概念同样适用于 Session、API Key 等其它凭证方案核心在于校验凭证 → 决定放行/拒绝这一抽象。整个示例由三个文件组成职责分离清晰文件职责关键依赖server.py创建应用、注册蓝图、定义受保护路由Sanic 核心login.py登录蓝图签发 JWTPyJWT、Sanic Blueprintauth.pyToken 校验与protected装饰器PyJWT、functools二、server.py应用入口与受保护路由from sanic import Sanic, text from auth import protected from login import login app Sanic(AuthApp) app.config.SECRET KEEP_IT_SECRET_KEEP_IT_SAFE app.blueprint(login) app.get(/secret) protected async def secret(request): return text(To go fast, you must be fast.)要点拆解app Sanic(AuthApp)创建 Sanic 应用实例app.config.SECRET ...通过应用配置存放 JWT 签名密钥。示例密钥KEEP_IT_SECRET_KEEP_IT_SAFE仅为演示生产环境务必使用足够长的随机密钥并通过环境变量或配置文件注入切勿硬编码进代码库app.blueprint(login)注册登录蓝图使/login/路由生效app.get(/secret)protected路由装饰器在上、认证装饰器在下。在 Python 中装饰器自下而上应用因此protected先于路由匹配执行——当请求到达时先校验 Token通过后再执行secret处理函数。这是 Sanic 中非常常见的认证 路由叠加写法与 decorators.md 中展示的多装饰器叠加风格一致例如app.get(/orders)之下叠加authorized(...)、validate_list_params()等。三、login.py登录蓝图与 JWT 签发import jwt from sanic import Blueprint, text login Blueprint(login, url_prefix/login) login.post(/) async def do_login(request): token jwt.encode({}, request.app.config.SECRET) return text(token)要点拆解Blueprint(login, url_prefix/login)创建名为login的蓝图并设置 URL 前缀。结合login.post(/)最终注册的路由为POST /login/。Blueprint 是 Sanic 组织路由的模块化手段使用url_prefix可将一组相关路由统一挂载到同一路径前缀之下关于 Blueprint 与url_prefix的更多用法可参考 blueprints.mdjwt.encode({}, request.app.config.SECRET)签发一个 payload 为空的 JWT。注意这里jwt.encode只传了两个位置参数PyJWT 会根据密钥自动推断签名算法HS256。由于SECRET是普通字符串而非 RSA 私钥对象将使用 HMAC 对称签名生产环境应把用户标识如sub、user_id、过期时间exp等声明写入 payloadrequest.app.config.SECRET在蓝图内通过request.app访问应用实例进而读取同一份SECRET配置保证签发与校验使用同一个密钥text(token)以纯文本返回 Token 字符串。实际项目中通常应返回 JSON例如{access_token: token}。依赖说明该示例依赖第三方库 PyJWTimport jwt需通过pip install PyJWT安装。Sanic 本身不捆绑 JWT 实现。四、auth.pyToken 校验与protected装饰器from functools import wraps import jwt from sanic import text def check_token(request): if not request.token: return False try: jwt.decode( request.token, request.app.config.SECRET, algorithms[HS256] ) except jwt.exceptions.InvalidTokenError: return False else: return True def protected(wrapped): def decorator(f): wraps(f) async def decorated_function(request, *args, **kwargs): is_authenticated check_token(request) if is_authenticated: response await f(request, *args, **kwargs) return response else: return text(You are unauthorized., 401) return decorated_function return decorator(wrapped)4.1check_token核心校验逻辑request.tokenSanic 提供的请求属性自动从Authorization请求头中解析出 Token详见下文第五节源码剖析。若请求头缺失或格式不对返回None因此首先判空短路jwt.decode(token, SECRET, algorithms[HS256])显式指定HS256算法解码校验。若 Token 被篡改、签名不匹配或已过期PyJWT 会抛出InvalidTokenError及其子类捕获后返回Falseelse: return True仅在解码成功未抛异常时返回True。4.2protected装饰器模板这是一个无参数装饰器模式对应 decorators.md 中 Without args 一节protected接收被装饰的函数wrapped返回内部decorator再由decorator包装出decorated_function。关键设计点wraps(f)复制原函数的元数据__name__、__doc__等保证路由命名、文档字符串、调试信息不受包装影响async def decorated_function(request, *args, **kwargs)包装函数保持与原处理函数相同的签名——首个参数为request后续*args/**kwargs用于透传路径参数与关键字参数例如带动态路由/user/user_id时鉴权放行校验通过后await f(request, *args, **kwargs)调用真实处理函数并原样返回其响应拒绝策略校验失败直接返回text(You are unauthorized., 401)——即返回 HTTP401 Unauthorized状态码与提示文本。4.3 从装饰器到授权的扩展思路check_token目前只回答Token 是否合法属于认证。若要做授权如区分普通用户与管理员可以在jwt.decode成功后取出 payload 中的角色声明如role与目标路由所需权限比对将装饰器参数化例如protected(roles[admin])对应 decorators.md 中 With args 的写法失败时按语义区分返回码凭证缺失/非法返回401权限不足返回403 Forbidden。Sanic 恰好为这两种情况都提供了内置异常详见第六节。五、源码级剖析request.token如何工作示例中反复出现的request.token并非示例代码自定义而是Sanic 请求对象的原生属性。其实现位于 sanic/request/types.pyproperty def token(self) - str | None: Attempt to return the auth header token. if self.parsed_token is None: prefixes (Bearer, Token) _, token parse_credentials( self.headers.getone(authorization, None), prefixes ) self.parsed_token token return self.parsed_token从中可以看出三个关键事实懒解析 缓存首次访问时解析结果存入parsed_token后续访问直接返回缓存值双前缀支持token属性同时识别Bearer与Token两种前缀二者等价底层解析函数parse_credentials位于 sanic/headers.py其默认识别(Basic, Bearer, Token)三种前缀实现为对请求头做前缀匹配并剥离前缀取余下部分def parse_credentials(header, prefixesNone): if not prefixes or not isinstance(prefixes, (list, tuple, set)): prefixes (Basic, Bearer, Token) if header is not None: for prefix in prefixes: if prefix in header: return prefix, header.partition(prefix)[-1].strip() return None, header隐含的解析规则Authorization: Bearer eyJ...会返回(Bearer, eyJ...)若请求头直接是裸 Token无前缀request.token也能正确取到。这一点被测试 tests/test_requests.py 中的test_token用例L371-L400显式验证——它参数化了无前缀裸 TokenToken前缀Bearer前缀无请求头四种场景断言request.token的解析结果。此外Sanic 还提供更完整的request.credentials属性sanic/request/types.py返回包含auth_type与token的Credentials对象覆盖 NoAuth、Basic Auth、Bearer Token、Api Token 四种认证方案Basic Auth 场景下还可访问username/password。test_credentialstests/test_requests.py对其进行了覆盖。如果你的方案需要区分认证类型可直接使用该属性不必自己解析请求头。六、更优雅的拒绝方式内置Unauthorized与Forbidden异常示例中直接用return text(You are unauthorized., 401)返回 401。Sanic 提供了更语义化的替代方案——抛出内置异常让错误处理框架统一接管。6.1Unauthorized401定义于 sanic/exceptions.py它支持额外的关键字参数自动补全WWW-Authentication响应头from sanic.exceptions import Unauthorized raise Unauthorized( Auth required., schemeBasic, realmRestricted Area, )对于 Digest 认证方案还可提供qop、algorithm、nonce、opaque等参数见 sanic/exceptions.py 中的示例Bearer 方案则可写为raise Unauthorized(Unauthorized, schemeBearer)此时响应头会带上WWW-Authenticate: Bearer——这在 tests/test_exceptions.py 与 L188 的断言中均有验证。6.2Forbidden403定义于 sanic/exceptions.py语义为已认证但权限不足适合授权场景。两者结合可将protected装饰器改写为from sanic.exceptions import Unauthorized async def decorated_function(request, *args, **kwargs): if check_token(request): return await f(request, *args, **kwargs) raise Unauthorized(You are unauthorized., schemeBearer)这样既复用了 Sanic 统一的异常处理与日志链路又让WWW-Authentication响应头正确下发客户端可以据此决定重新认证的方式。七、端到端验证curl完整演练将三个文件置于同一目录server.py、login.py、auth.py安装依赖后启动服务pip install sanic PyJWT sanic server.app随后按文档中的curl流程验证四种场景。场景 1未携带 Token 访问受保护资源 —— 返回 401$ curl localhost:9999/secret -i HTTP/1.1 401 Unauthorized content-length: 21 connection: keep-alive content-type: text/plain; charsetutf-8 You are unauthorized.场景 2调用登录接口获取 JWT$ curl localhost:9999/login -X POST eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE返回的即是一个典型的 JWT三段以.分隔分别对应 HeadereyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9、Payloade30即空对象{}与签名。场景 3携带合法 Token 访问 —— 返回 200$ curl localhost:9999/secret -i -H Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE HTTP/1.1 200 OK content-length: 29 connection: keep-alive content-type: text/plain; charsetutf-8 To go fast, you must be fast.场景 4携带被篡改的 Token —— 返回 401$ curl localhost:9999/secret -i -H Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.BAD HTTP/1.1 401 Unauthorized content-length: 21 connection: keep-alive content-type: text/plain; charsetutf-8 You are unauthorized.场景 3 与场景 4 的对比直观展示了 JWT 的完整性保护签名部分被篡改后jwt.decode立即抛出InvalidTokenError被check_token捕获并判定为未认证。另外注意场景 3 中Bearer前缀正是被 sanic/request/types.py 中request.token的(Bearer, Token)前缀列表所识别与场景 4 同链路解析。八、进阶方向与生产化建议装饰器 vs 中间件protected装饰器适合逐路由保护若需要全局保护或需要访问request之外更丰富的上下文如改写请求对象、注入用户信息可考虑 Sanic 中间件app.middleware方案。装饰器模式的完整模板带参数、不带参数、可带可不带参数三种形态见 decorators.mdToken 生命周期生产环境应在 JWT payload 中加入exp过期时间、iat签发时间与sub用户标识声明并实现刷新refresh与吊销revocation机制密钥管理SECRET应来自环境变量或密钥管理服务轮换时需考虑新旧密钥并存过渡HTTPSToken 经Authorization头传输务必全链路启用 TLS防止中间人窃取。Sanic 的 TLS 配置可参考 guide/content/en/guide/deployment/tls.md社区生态文中提到社区还有更完整的认证/会话扩展与深入讲解资源如 Awesome Sanic 的 Authorization 与 Session 列表、EuroPython 2020 关于 Web API 访问控制的演讲涉及角色权限、多方案混合、OAuth 流程等更复杂的场景时可以参考。总结本文完整复现并深入解析了 Sanic 官方认证示例以三个职责单一的文件入口 登录蓝图 校验装饰器实现了基于 JWT 的认证闭环并通过curl验证了未认证 401 → 登录拿 Token → 携带 Token 200 → 篡改 Token 401的完整状态流转。在此基础上结合源码说明了request.token的解析机制sanic/request/types.py、sanic/headers.py与内置Unauthorized/Forbidden异常的用法sanic/exceptions.py、sanic/exceptions.py并有对应测试tests/test_requests.py作为事实支撑。无论是直接复制使用还是在其上演进为角色授权、全局中间件等更复杂的方案本文给出的模式都具备直接的可移植性。赞分享后端Web框架【免费下载链接】sanicAccelerate your web app development | Build fast. Run fast.项目地址https://gitcode.com/gh_mirrors/sa/sanic点击查看免费下载相关推荐终极指南如何在Go中实现JWT认证、OAuth2授权与RBAC权限控制终极指南如何在Go中实现JWT认证、OAuth2授权与RBAC权限控制 在当今的Web应用开发中安全性是不可或缺的核心要素。Go语言凭借其高效的性能和简洁的文档知识库RedisInsight批量操作深度解析5个提升Redis管理效率的关键技巧RedisInsight批量操作深度解析5个提升Redis管理效率的关键技巧 RedisInsight作为Redis官方推出的图形化管理工具其批量操作功能是数据库客户端桌面应用后端前端数据可视化Sanic认证授权终极指南JWT与OAuth2集成方案详解Sanic认证授权终极指南JWT与OAuth2集成方案详解 Sanic作为一款高性能的Python Web框架以Build fast. Run fast.后端Web框架上一篇Bash集合操作实战从基础到高级操作详解下一篇深入理解枚举类型从基础应用到状态管理系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考