【免费下载链接】authlib
The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS, JWE, JWK, JWA, JWT included.
导读
本文基于 Authlib 官方文档 docs/oauth1/provider/django/authorization-server.rst,完整讲解如何在 Django 项目中搭建 OAuth 1.0(RFC 5849)授权服务器。你将学会:用CacheAuthorizationServer一行代码完成服务端初始化,通过三个视图函数实现"临时凭证签发 → 资源所有者授权 → 令牌凭证兑换"的完整流程,并结合仓库源码理解临时凭证缓存、nonce 防重放、签名校验等底层机制。
授权服务器:三个端点,一次初始化
OAuth 1.0 授权服务器(Authorization Server)对外提供三类关键端点:
- 临时凭证端点(Temporary Credential Endpoint):客户端通过它获取临时凭证(request token);
- 资源所有者授权端点(Authorization Endpoint):客户端将用户重定向到此页面,由资源所有者(用户)批准或拒绝授权请求;
- 令牌凭证端点(Token Endpoint):客户端使用临时凭证和用户授权的
oauth_verifier,兑换正式的令牌凭证。
当资源所有者批准授权后,授权服务器即向客户端签发令牌凭证(token credential),客户端凭此访问受保护资源。
Authlib 的 Django 实现大量依赖缓存(cache)。这意味着你无需自行维护临时凭证、时间戳(timestamp)和 nonce 的去重逻辑——这些都已内置实现(源码见 authlib/integrations/django_oauth1/authorization_server.py)。
创建授权服务器非常简单,只需要提供Client和Token两个模型:
from your_project.models import Client, Token from authlib.integrations.django_oauth1 import CacheAuthorizationServer authorization_server = CacheAuthorizationServer(Client, Token)从源码结构看,django_oauth1集成模块(authlib/integrations/django_oauth1/init.py)对外导出BaseServer、CacheAuthorizationServer和ResourceProtector,其中CacheAuthorizationServer继承自BaseServer,而BaseServer又继承自 RFC 5849 的通用AuthorizationServer(authlib/oauth1/rfc5849/authorization_server.py)。
开发小贴士:在本地(localhost)开发时,OAuth 1.0 要求 HTTPS 传输。若在本地调试,需要先设置环境变量(详见 docs/oauth1/provider/django/index.rst):
export AUTHLIB_INSECURE_TRANSPORT=true
Resource Owner:直接复用 Django 内置 User
资源所有者(Resource Owner)就是正在使用你服务的用户,他可以通过用户名/邮箱加密码,或其他方式登录你的网站。在 Django 中,直接使用内置的contrib用户模型即可:
from django.contrib.auth.models import UserClient:实现 ClientMixin 的三个方法
客户端(Client)是一个"代表资源所有者、并征得其授权后发起受保护资源请求"的应用程序。它至少包含三类信息:
- Client Identifier,通常称为
client_id; - Client Password,通常称为
client_secret; - Client RSA Public Key(如果支持 RSA-SHA1 签名方法)。
Authlib 没有内置 Django 的 Client 模型,需要你自行实现。结合ClientMixin(定义见 authlib/oauth1/rfc5849/models.py),标准写法如下:
from django.db import models from django.contrib.auth.models import User from authlib.oauth1 import ClientMixin class Client(models.Model, ClientMixin): user = models.ForeignKey(User, on_delete=CASCADE) client_id = models.CharField(max_length=48, unique=True, db_index=True) client_secret = models.CharField(max_length=48, blank=True) default_redirect_uri = models.TextField(blank=False, default='') def get_default_redirect_uri(self): return self.default_redirect_uri def get_client_secret(self): return self.client_secret def get_rsa_public_key(self): return NoneClientMixin要求实现的方法与用途:
| 方法 | 用途 |
|---|---|
get_default_redirect_uri() | 返回客户端默认回调地址(当临时凭证未携带oauth_callback或为oob时使用) |
get_client_secret() | 返回client_secret,用于 HMAC-SHA1 / PLAINTEXT 签名计算 |
get_rsa_public_key() | 返回 RSA 公钥,仅在使用 RSA-SHA1 签名方法时需要,否则返回None |
客户端由网站上的开发者(即某个User)注册。仓库测试中的示例模型(tests/django/test_oauth1/models.py)与上述写法一致,区别仅在于测试版get_rsa_public_key()会返回测试密钥文件。
Token:不设过期时间的持久化凭证
令牌凭证(token credential)用于访问资源所有者的资源。与 OAuth 2.0 不同,OAuth 1.0 的令牌凭证不会过期。因此它应当保存到持久化数据库,而非缓存。Django 中的实现示例如下:
from django.db import models from django.contrib.auth.models import User from authlib.oauth1 import TokenCredentialMixin class Token(models.Model, TokenCredentialMixin): user = models.ForeignKey(User, on_delete=CASCADE) client_id = models.CharField(max_length=48, db_index=True) oauth_token = models.CharField(max_length=84, unique=True, db_index=True) oauth_token_secret = models.CharField(max_length=84) def get_oauth_token(self): return self.oauth_token def get_oauth_token_secret(self): return self.oauth_token_secretTokenCredentialMixin需要实现get_oauth_token()与get_oauth_token_secret()两个方法(见 authlib/oauth1/rfc5849/models.py)。从 authlib/integrations/django_oauth1/authorization_server.py 的create_token_credential源码可以看到,兑换令牌时服务器会用temporary_credential.get_user_id()和temporary_credential.get_client_id()自动填充user_id与client_id字段,然后保存入库:
def create_token_credential(self, request): temporary_credential = request.credential token = self.token_generator() item = self.token_model( oauth_token=token["oauth_token"], oauth_token_secret=token["oauth_token_secret"], user_id=temporary_credential.get_user_id(), client_id=temporary_credential.get_client_id(), ) item.save() return item服务端实现:三个视图函数
完成模型后,就可以创建授权与签发令牌的端点视图了。
1. 临时凭证端点
该端点供客户端获取临时凭证,仅接受 POST 请求:
from django.views.decorators.http import require_http_methods @require_http_methods(["POST"]) def initiate_temporary_credential(request): return server.create_temporary_credential_response(request)在底层,authlib/oauth1/rfc5849/authorization_server.py 的validate_temporary_credentials_request会依次校验:HTTP 方法必须为 POST、必须携带oauth_consumer_key、必须携带合法oauth_callback(允许字面量"oob"或绝对 URI)、客户端必须存在,随后校验时间戳/nonce 与签名。通过后返回形如下面的响应体:
oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&oauth_callback_confirmed=truecreate_temporary_credential的具体落库逻辑由 Django 集成层实现(见下文"缓存与配置"小节)。
2. 资源所有者授权端点
客户端会将用户重定向到此授权页面,由资源所有者批准或拒绝请求:
from django.shortcuts import render def authorize(request): # 请确保用户已自行登录(此处未包含登录校验逻辑) if request.method == 'GET': try: req = server.check_authorization_request(request) context = {'req': req} return render(request, 'authorize.html', context) except OAuth1Error as error: context = {'error': error} return render(request, 'error.html', context) granted = request.POST.get('granted') if granted: grant_user = request.user else: grant_user = None try: return server.create_authorization_response(request, grant_user) except OAuth1Error as error: context = {'error': error} return render(request, 'error.html', context)该视图的核心逻辑是:GET 请求时先check_authorization_request校验oauth_token是否对应一份有效的临时凭证(缺少oauth_token抛MissingRequiredParameterError,临时凭证不存在抛InvalidTokenError,见源码validate_authorization_request);POST 时根据表单中的granted字段决定grant_user,然后调用create_authorization_response。
从 authlib/oauth1/rfc5849/authorization_server.py 的create_authorization_response源码可以看到两种结果:
- 用户拒绝(
grant_user is None):以 302 重定向到回调地址,并附带access_denied错误参数; - 用户批准:生成一个 36 字符的
oauth_verifier,将其与user_id一起写回缓存中的临时凭证(见create_authorization_verifier),然后以 302 重定向,回调地址带oauth_token与oauth_verifier两个参数:
http://printer.example.com/ready?oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884当临时凭证没有回调地址或为"oob"时,服务器会回退到该客户端模型的get_default_redirect_uri()。
3. 令牌凭证端点
客户端携带临时凭证和资源所有者授权的oauth_verifier来兑换令牌凭证:
from django.views.decorators.http import require_http_methods @require_http_methods(["POST"]) def issue_token(request): return server.create_token_response(request)validate_token_request的校验顺序(见源码)为:oauth_consumer_key必须存在 → 客户端必须存在 →oauth_token必须存在 → 临时凭证必须有效 →oauth_verifier必须与临时凭证匹配(token.check_verifier(verifier))→ 校验时间戳/nonce 与签名。全部通过后create_token_response生成令牌并删除临时凭证,返回:
oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00注册 URL 路由
最后,把这几个视图注册到 URL 配置中。例如:
# urls.py from django.urls import path urlpatterns = [ path('oauth/initiate', initiate_temporary_credential), path('oauth/authorize', authorize), path('oauth/token', issue_token), ]仓库测试(tests/django/test_oauth1/test_authorize.py)正是使用/oauth/initiate、/oauth/authorize、/oauth/token这三个路径来验证完整流程的。
缓存与配置:AUTHLIB_OAUTH1_PROVIDER 详解
CacheAuthorizationServer之所以"开箱即用",是因为临时凭证、nonce 都存放在 Django 缓存中(默认django.core.cache)。相关配置统一放在 Django 设置文件的AUTHLIB_OAUTH1_PROVIDER字典里(源码见 authlib/integrations/django_oauth1/authorization_server.py):
# settings.py AUTHLIB_OAUTH1_PROVIDER = { "nonce_expires_in": 86400, # nonce 缓存有效期(秒),默认 86400 "signature_methods": ["HMAC-SHA1", "PLAINTEXT"], # 支持的签名方法,默认 ["HMAC-SHA1"] "temporary_credential_expires_in": 86400, # 临时凭证缓存有效期(秒),默认 86400 "temporary_credential_key_prefix": "temporary_credential:", # 临时凭证缓存键前缀 }各配置项的实际作用:
nonce_expires_in:nonce 防重放记录在缓存中的保留时长,默认 86400 秒。nonce的缓存键在 authlib/integrations/django_oauth1/nonce.py 中构造为nonce:{nonce}-{timestamp}-{client_id},若请求携带oauth_token还会追加-{token};signature_methods:覆盖默认支持的签名方法列表。默认仅支持HMAC-SHA1(定义于 authlib/oauth1/rfc5849/base_server.py 的SUPPORTED_SIGNATURE_METHODS),可扩展为RSA-SHA1、PLAINTEXT;temporary_credential_expires_in与temporary_credential_key_prefix:控制临时凭证在缓存中的过期时间与键前缀。
create_temporary_credential的缓存写入逻辑(源码)如下:以key_prefix + oauth_token为键,把client_id、oauth_callback与令牌一起写入缓存,并设置timeout:
def create_temporary_credential(self, request): key_prefix = self._temporary_credential_key_prefix token = self.token_generator() client_id = request.client_id redirect_uri = request.redirect_uri key = key_prefix + token["oauth_token"] token["client_id"] = client_id if redirect_uri: token["oauth_callback"] = redirect_uri cache.set(key, token, timeout=self._temporary_expires_in) return TemporaryCredential(token)此外,token_generator默认生成 42 字符的oauth_token与 48 字符的oauth_token_secret;你也可以在构造服务器时传入自定义生成器:
authorization_server = CacheAuthorizationServer(Client, Token, token_generator=my_generator)底层机制:时间戳、nonce 与签名校验
OAuth 1.0 的安全性依赖签名与防重放机制,这部分在 authlib/oauth1/rfc5849/base_server.py 中统一实现:
- 时间戳校验:
oauth_timestamp必须是正整数,且与当前时间的差值不能超过EXPIRY_TIME(默认 300 秒),超出即拒绝。使用PLAINTEXT签名方法时,时间戳和 nonce 可以省略; - nonce 防重放:同一 nonce 在同一时间戳、同一客户端(及同一令牌)组合下必须唯一,重复使用抛
InvalidNonceError; - 签名校验:
SUPPORTED_SIGNATURE_METHODS之外的签名方法抛UnsupportedSignatureMethodError,签名不匹配抛InvalidSignatureError。内置三种校验器(authlib/oauth1/rfc5849/signature.py):HMAC-SHA1、RSA-SHA1、PLAINTEXT,签名基串按 RFC 5849 3.4.1 节构造。
响应头也遵循 OAuth 规范:令牌类响应统一携带Content-Type: application/x-www-form-urlencoded、Cache-Control: no-store、Pragma: no-cache(见TOKEN_RESPONSE_HEADER)。
测试验证与资源服务器配套
仓库为这套 Django OAuth 1.0 集成提供了完整的测试覆盖(目录 tests/django/test_oauth1/):
- conftest.py:构造
CacheAuthorizationServer(Client, TokenCredential)测试服务器,并分别提供PLAINTEXT、RSA-SHA1签名方法的配置化 fixture; - test_authorize.py:验证授权端点——无效
oauth_token抛InvalidTokenError,用户拒绝返回 302 且带access_denied,用户批准返回带oauth_verifier的 302; - test_token_credentials.py:验证令牌兑换端点的参数缺失、无效客户端、无效令牌、重复 OAuth 参数,以及三种签名方法的成功与失败路径(含 nonce 重用导致的
invalid_nonce); - test_resource_protector.py:验证受保护资源端点的访问控制。
授权服务器签发令牌之后,还需要保护你的用户资源,这由同模块的ResourceProtector完成(详见 docs/oauth1/provider/django/resource-server.rst)。它作为装饰器使用,通过后会在request.oauth1_credential中注入 Token 实例:
from django.http import JsonResponse from authlib.integrations.django_oauth1 import ResourceProtector require_oauth = ResourceProtector(Client, TokenCredential) @require_oauth def user_api(request): user = request.oauth1_credential.user return JsonResponse(dict(username=user.username))至此,一个完整的 Django OAuth 1.0 提供方(授权服务器 + 资源服务器)即搭建完成:客户端发起临时凭证请求 → 用户授权 → 兑换令牌 → 携带令牌访问受保护资源,全链路均由 Authlib 的缓存内置逻辑与签名校验保驾护航。
【免费下载链接】authlib
The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS, JWE, JWK, JWA, JWT included.
相关推荐
Authlib OAuth 1.0服务器搭建:RFC5849协议完整实现
Authlib OAuth 1.0服务器搭建:RFC5849协议完整实现 Authlib是Python生态中构建OAuth和OpenID Connect客户端与
Erlang/OTP 30 移除计划详解:Guard 类型测试别名、分布式协议与加密组件弃用清单
Erlang/OTP 30 移除计划详解:Guard 类型测试别名、分布式协议与加密组件弃用清单 本指南基于 Erlang/OTP 仓库中的 OTP 30 计划
WPScan 插件版本识别实战:以 Chaport 的 changelog.md 为样本,理解 ChangeLog 动态查找器的工作机制
WPScan 插件版本识别实战:以 Chaport 的 changelog.md 为样本,理解 ChangeLog 动态查找器的工作机制 导读 本文以仓库中 c
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考