9.2 KiB
技术文档:基于 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 装饰器注册为全局中间件,负责:
- 记录访问开始时间
- 获取当前用户身份
- 调用
checkUserPermission()判断是否有权访问路径 - 记录耗时与异常信息
- 控制响应流程或抛出
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)
此方法完成整个认证系统的初始化,步骤如下:
-
构建 secret 密钥
基于端口号拼接固定字符串,补足 32 字节用于 AES 加密(Cookie Storage 所需) -
选择会话存储方式
if self.conf.website.session_redis: redisdb = await redis.Redis.from_url(url) storage = MyRedisStorage(redisdb) else: storage = EncryptedCookieStorage(secret) -
安装会话中间件
aiohttp_session.setup(app, storage) -
配置 Ticket 认证策略
policy = SessionTktAuthentication( secret=secret, max_age=session_max_time, reissue_time=session_reissue_time, include_ip=True ) auth.setup(app, policy) -
替换默认 IP 获取逻辑
TktAuthentication._get_ip = get_client_ip # 使用 request['client_ip'] -
注入权限检查中间件
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 公共技术组件团队