ahserver/aidocs/auth_api.md
2025-10-05 12:07:12 +08:00

9.2 KiB
Raw Blame History

技术文档:基于 aiohttp 的认证与会话管理系统


概述

本文档描述了一个基于 aiohttp 构建的异步 Web 应用中的 用户认证Authentication会话管理Session Management 系统。该系统支持:

  • 基于 Cookie 或 Redis 存储的会话机制
  • 使用加密 Ticket 的安全认证策略(TktAuthentication
  • 支持客户端唯一标识(client_uuid)绑定
  • RSA 加密解密功能用于敏感数据处理
  • 可扩展的权限检查接口
  • 请求日志记录与异常追踪

该模块适用于需要高安全性、可扩展性和分布式部署能力的 Web 后端服务。


依赖库说明

包名 用途
aiohttp 异步 Web 框架核心
aiohttp_auth / TktAuthentication 基于票据ticket的身份认证中间件
aiohttp_session 会话管理中间件,支持多种存储后端
redis.asyncio 异步 Redis 客户端,用于持久化会话
cryptography(隐式依赖) 加密 Cookie 所需(由 EncryptedCookieStorage 内部使用)
appPublic.* 自定义公共工具模块配置、日志、RSA 加解密等)

⚠️ 注意:原代码中注释了 aioredis,实际使用的是新版本 redis.asyncio


核心功能模块

1. 会话管理Session Management

支持两种会话存储方式:

存储类型 配置开关 特点
加密 Cookie 存储 默认启用 无外部依赖,适合单机部署
Redis 存储 conf.website.session_redis.url 存在时启用 支持集群、集中管理、更安全

自定义 Redis 存储类:MyRedisStorage

继承自 RedisStorage,增强以下特性:

  • 自定义 Session Key 生成逻辑
    • 优先从请求头 client_uuid 获取客户端唯一标识
    • 若不存在,则随机生成 UUID4 并作为响应返回给客户端
    • 对字符串 key 进行 hex 编码以保证兼容性
def key_gen(self, request):
    key = request.headers.get('client_uuid')
    if not key:
        key = uuid.uuid4().hex
        return key
    if isinstance(key, str):
        key = key.encode('utf-8')
    key = binascii.hexlify(key).decode('utf-8')
    return key

目的:实现跨设备/浏览器的稳定会话识别,避免频繁重新登录。


2. 认证机制Authentication

使用 aiohttp_auth.auth.ticket_auth.TktAuthentication 实现基于时间戳和签名的票据认证。

关键参数配置:

参数 默认值 配置项 说明
session_max_time 120 秒 website.session_max_time 会话最长有效期
reissue_time 30 秒 website.session_reissue_time 自动续签间隔
include_ip True 固定设置 将客户端 IP 加入票据哈希,防劫持

自定义 _new_ticket 方法

重写了票据创建过程,加入对 client_uuid 的支持:

def _new_ticket(self, request, user_id):
    client_uuid = request.headers.get('client_uuid')
    ip = self._get_ip(request)
    valid_until = int(time.time()) + self._max_age
    return self._ticket.new(
        user_id,
        valid_until=valid_until,
        client_ip=ip,
        user_data=client_uuid  # 将 client_uuid 附加到票据中
    )

🔐 安全提示IP 绑定 + client_uuid 提升了会话安全性,防止 CSRF 和会话固定攻击。


3. 用户信息提取

提供异步函数用于从当前会话中获取用户信息。

函数列表

函数 返回值 说明
get_session_userinfo(request) DictObject(userid, username, userorgid) 解析认证数据并封装为对象
get_session_user(request) userid 字符串 快速获取当前用户 ID

💡 数据格式:认证信息以 userid:username:userorgid 形式存储于 ticket 中。

示例:

await auth.remember(request, "U1001:alice:ORG789")

4. 登录与登出操作

user_login(request, userid, username='', userorgid='')

将用户信息编码后写入认证票据。

ui = f'{userid}:{username}:{userorgid}'
await auth.remember(request, ui)

user_logout(request)

清除认证状态。

await auth.forget(request)

🧽 清除的是服务器端票据与客户端 Cookie。


5. 权限控制中间件:checkAuth

通过 @web.middleware 装饰器注册为全局中间件,负责:

  1. 记录访问开始时间
  2. 获取当前用户身份
  3. 调用 checkUserPermission() 判断是否有权访问路径
  4. 记录耗时与异常信息
  5. 控制响应流程或抛出 HTTPUnauthorized / HTTPForbidden

日志输出示例

INFO timecost=client(192.168.1.100) U1001 access /api/data cost 0.045, (0.002)
ERROR Exception=client(192.168.1.100) U1001 access /api/admin/delete cost 0.12, (0.003), except=ValueError...

成功请求记录总耗时及权限判断耗时;异常则完整打印 traceback。


6. RSA 加解密支持

类方法:AuthAPI

方法 功能
getPrivateKey() 延迟加载私钥文件(仅首次调用读取),避免重复 IO
rsaDecode(cdata) 使用私钥解密 Base64 编码的数据

依赖:

  • appPublic.rsawrap.RSA:封装了 PyCryptodome 的 RSA 操作
  • 配置路径:conf.website.rsakey.privatekey

典型用途:解密前端传来的加密密码或其他敏感字段。


7. 初始化与集成:setupAuth(app)

此方法完成整个认证系统的初始化,步骤如下:

  1. 构建 secret 密钥
    基于端口号拼接固定字符串,补足 32 字节用于 AES 加密Cookie Storage 所需)

  2. 选择会话存储方式

    if self.conf.website.session_redis:
        redisdb = await redis.Redis.from_url(url)
        storage = MyRedisStorage(redisdb)
    else:
        storage = EncryptedCookieStorage(secret)
    
  3. 安装会话中间件

    aiohttp_session.setup(app, storage)
    
  4. 配置 Ticket 认证策略

    policy = SessionTktAuthentication(
        secret=secret,
        max_age=session_max_time,
        reissue_time=session_reissue_time,
        include_ip=True
    )
    auth.setup(app, policy)
    
  5. 替换默认 IP 获取逻辑

    TktAuthentication._get_ip = get_client_ip  # 使用 request['client_ip']
    
  6. 注入权限检查中间件

    app.middlewares.append(self.checkAuth)
    

配置要求(jsonConfig 结构)

getConfig() 应返回包含以下字段的配置对象:

{
  "website": {
    "port": 8080,
    "rsakey": {
      "privatekey": "/path/to/private.pem"
    },
    "session_max_time": 3600,
    "session_reissue_time": 1800,
    "session_redis": {
      "url": "redis://localhost:6379/0"
    }
  }
}

⚠️ 若未设置 session_redis.url,则自动降级为本地加密 Cookie 存储。


使用方式(集成到 AIOHTTP 应用)

from aiohttp import web
from your_module import AuthAPI

async def init_app():
    app = web.Application()
    
    auth_api = AuthAPI()
    await auth_api.setupAuth(app)

    # 添加路由
    # app.router.add_get('/protected', protected_handler)

    return app

if __name__ == '__main__':
    web.run_app(init_app(), port=8080)

安全建议

项目 推荐做法
Secret Key 不应硬编码,建议从环境变量或密钥管理系统加载
client_uuid 前端应在首次访问时生成并持久化localStorage每次请求带上
Redis 安全 开启密码认证、限制网络访问
日志敏感信息 禁止记录用户密码、token 明文
HTTPS 生产环境必须启用 TLS防止 Cookie 被窃听

扩展接口

checkUserPermission(request, user, path)

抽象方法,子类可覆盖实现 RBAC、ACL 等权限模型。

async def checkUserPermission(self, request, user, path):
    # 示例:仅允许特定用户访问管理员接口
    if path.startswith("/admin") and user != "admin":
        return False
    return True

needAuth(path)

预留钩子,未来可用于跳过某些路径的认证检查。


错误处理

异常场景 处理方式
未登录访问受保护资源 抛出 HTTPUnauthorized (401)
有登录但无权限 抛出 HTTPForbidden (403)
内部错误 捕获并记录 traceback重新抛出异常
Redis 连接失败 初始化阶段抛出异常,应用无法启动

性能考量

  • Redis 存储模式:增加一次网络往返,但支持横向扩展
  • Ticket 验证:轻量级 HMAC 验证,性能优异
  • 自动续签机制:每 reissue_time 秒更新票据,延长会话寿命而不影响用户体验

总结

本模块提供了一套完整的、安全的、可扩展的异步认证解决方案,特点包括:

支持分布式部署Redis
客户端绑定UUID + IP提升安全性
细粒度权限控制接口
全链路日志跟踪与性能监控
支持 RSA 解密敏感数据

适用于企业级后台管理系统、API 网关、微服务认证中心等场景。


📝 文档版本v1.0
© 2025 公共技术组件团队