Python实现阿里云OSS直传签名生成与回调验签完整示例

周予安
周予安
发布时间:2026-10-15| 2人浏览过
原创

本文详细介绍如何使用Python生成阿里云OSS的直传签名(Policy与Signature)并配置上传回调。通过示例代码展示如何构建上传策略、计算签名以及在后端服务中验证OSS回调请求的身份,确保文件上传的安全性与完整性。

生成直传签名与回调配置

首先,我们需要在服务器端生成用于前端直传阿里云OSS的签名信息。这包括AccessKey ID、Host地址、Base64编码后的Policy以及HMAC-SHA1计算出的签名。同时,还需配置上传回调URL及回调Body,以便OSS在上传成功后通知应用服务器。

签名生成代码实现


import base64
import json
import time
from datetime import datetime
import hmac
from hashlib import sha1

access_key_id = ''
# 请填写您的AccessKeySecret。
access_key_secret = ''
# host的格式为 bucketname.endpoint ,请替换为您的真实信息。
host = ''
# callback_url为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
callback_url = ""
# 用户上传文件时指定的前缀。
upload_dir = 'user-dir-prefix/'
expire_time = 1200
expire_syncpoint = int(time.time() + expire_time)

policy_dict = {
  'expiration': datetime.utcfromtimestamp(expire_syncpoint).isoformat() + 'Z',
  'conditions': [
    {"bucket": "test-paige"},
    ['starts-with', '$key', 'user/test/']
  ]
}
policy = json.dumps(policy_dict).strip()
policy_encode = base64.b64encode(policy.encode())
signature = base64.encodebytes(hmac.new(access_key_secret.encode(), policy_encode, sha1).digest())

callback_dict = {
  'callbackUrl': callback_url,
  'callbackBody': 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${'
          'imageInfo.width}',
  'callbackBodyType': 'application/json'
}

callback = base64.b64encode(json.dumps(callback_dict).strip().encode()).decode()

var = {
  'accessid': access_key_id,
  'host': host,
  'policy': policy_encode.decode(),
  'signature': signature.decode().strip(),
  'expire': expire_syncpoint,
  'callback': callback
}

实现回调请求验证

接下来,在应用服务器端实现回调验证逻辑。当OSS完成上传后,会向指定的callbackUrl发送POST请求。服务器需要解析请求头中的x-oss-pub-key-url,获取OSS的公钥,并使用该公钥验证Authorization头中的签名,从而确认请求确实来自阿里云OSS,防止伪造回调。

回调验证代码实现


import asyncio
import base64
import time
import aiomysql
import rsa
from aiohttp import web, ClientSession
from urllib import parse
import uuid


def success(msg='', data=None):
  if data is None:
    data = {}
  dict_data = {
    'code': 1,
    'msg': msg,
    'data': data
  }
  return web.json_response(dict_data)


def failed(msg='', data=None):
  if data is None:
    data = {}
  dict_data = {
    'code': 0,
    'msg': msg,
    'data': data
  }
  return web.json_response(dict_data)


async def handle(request):
  """
  获取连接池
  :param web.BaseRequest request:
  :return:
  """
  authorization_base64 = request.headers['authorization']
  x_oss_pub_key_url_base64 = request.headers['x-oss-pub-key-url']
  pub_key_url = base64.b64decode(x_oss_pub_key_url_base64.encode())
  authorization = base64.b64decode(authorization_base64.encode())
  path = request.path

  async with ClientSession() as session:
    async with session.get(pub_key_url.decode()) as resp:
      pub_key_body = await resp.text()
      pubkey = rsa.PublicKey.load_pkcs1_openssl_pem(pub_key_body.encode())
      body = await request.content.read()
      auth_str = parse.unquote(path) + '\n' + body.decode()
      parse_url = parse.parse_qs(body.decode())
      print(parse_url)
      try:
        rsa.verify(auth_str.encode(), authorization, pubkey)
        pool = request.app['mysql_pool']
        async with pool.acquire() as conn:
          async with conn.cursor() as cur:
            id = str(uuid.uuid4())
            url = parse_url['filename'][0]
            mime = parse_url['mimeType'][0]
            disk = 'oss'
            time_at = time.strftime("%Y-%m-%d %H:%I:%S", time.localtime())
            sql = "INSERT INTO media(id,url,mime,disk,created_at,updated_at) VALUES(%s,%s,%s,%s,%s,%s)"
            await cur.execute(sql, (id, url, mime, disk, time_at, time_at))
            await conn.commit()
        dict_data = {
          'id': id,
          'url': url,
          'cdn_url': 'https://cdn.***.net' + '/' + url,
          'mime': mime,
          'disk': disk,
          'created_at': time_at,
          'updated_at': time_at,
        }
        return success(data=dict_data)
      except rsa.pkcs1.VerificationError:
        return failed(msg='验证错误')


async def init(loop):
  # 创建连接池
  mysql_pool = await aiomysql.create_pool(host='127.0.0.1', port=3306,
                      user='', password='',
                      db='', loop=loop)

  async def on_shutdown(application):
    """
    接收到关闭信号时,要先关闭连接池,并等待连接池关闭成功.
    :param web.Application application:
    :return:
    """
    application['mysql_pool'].close()
    # 没有下面这句话会报错 RuntimeError: Event loop is closed ,因为连接池没有真正关关闭程序就关闭了,引发python的报错
    await application['mysql_pool'].wait_closed()

  application = web.Application()
  application.on_shutdown.append(on_shutdown)
  # 把连接池放到 application 实例中
  application['mysql_pool'] = mysql_pool
  application.add_routes([web.get('/', handle), web.post('/oss', handle)])
  return application


if __name__ == '__main__':
  loop = asyncio.get_event_loop()
  application = loop.run_until_complete(init(loop))
  web.run_app(application, host='127.0.0.1')
  loop.close()

至此,关于Python实现阿里云OSS直传签名生成与回调验证的完整流程已介绍完毕。更多相关技术细节可参考本站往期文章或继续浏览后续内容,感谢大家的支持。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。