Skip to content

日志与安全 ​

概述 ​

后端在 framework 层提供了一套与业务解耦的校验与安全能力,覆盖请求入参校验、富文本/文件清洗、密码与敏感数据加密、以及统一日志。它们统一被各业务模块(module_system、module_infra、module_ai 等)复用,开发者无需重复实现。

日志 ​

common/logger.py 基于 loguru 统一日志体系,在 app_init 启动时 setup_logger() 完成初始化:

  • 双通道输出:控制台 + 文件(logs/fastapi.log),文件按天轮转(rotation)、保留 30 天(retention)、压缩为 zip。
  • 级别:控制台级别由 settings.LOGGER_LEVEL 控制,文件固定 INFO。
  • 关联 ID:通过 _context_patcher 把请求级 correlation_id(来自 web/middleware)注入每条日志,便于跨服务/跨请求串联排查。
  • 标准日志桥接:InterceptHandler 将 uvicorn 等标准库日志统一接管到 loguru;AccessLogFilter 屏蔽 /infra/health 健康检查探针日志,避免刷屏。

业务代码直接使用 from app.framework.common.logger import logger 即可:

python
from app.framework.common.logger import logger

logger.info(f"用户 {user_id} 登录成功 | cid={...}")
logger.warning("文件类型不匹配: 声明扩展名=%s, 检测类型=%s", ext, detected)

不要在日志中打印明文密码、令牌等敏感信息;异常堆栈统一交给异常处理器记录,业务层无需 try/except 后吞掉错误。

输入校验 ​

校验基于 Pydantic 的 Annotated + AfterValidator 实现,以「可复用类型」的形式沉淀在 app/framework/validation/ 中。业务 Schema 只需在字段上标注对应类型,即可在请求解析阶段自动完成格式校验,并在序列化时统一输出格式。

三类内置校验类型:

  • 编码校验 code.py:限制编码以字母开头,仅允许字母、数字、下划线(长度与正则由具体函数约束)。常用于角色编码、部门编码、字典编码等需要作为唯一业务标识的场景。
  • 联系方式校验 contact.py:提供手机号 Telephone 与邮箱 Email 两种类型,采用国内手机号段正则与标准邮箱正则,空值放行(数据库以空串表示未填)。
  • 日期时间校验 datetime.py:提供 DateTimeStr、DateStr、TimeStr,统一按 app/config/constants.py 中约定的展示格式(DATETIME_DISPLAY_FMT 等)解析并序列化,避免前后端时间格式不一致。

在 Schema 中使用示例(用户模块):

python
from app.framework.validation.contact import Email, Telephone
from app.framework.validation.datetime import DateTimeStr

class UserCreateSchema(BaseModel):
    mobile: Telephone | None = Field(default=None, max_length=11, description="手机号")
    email: Email | None = Field(default=None, description="邮箱")
    create_time: DateTimeStr | None = Field(default=None, description="创建时间")

也可以配合 field_validator 做跨字段或枚举约束(如公告状态只允许 0/1)。校验失败时统一抛出 CustomException,由 Web 层异常处理器转换为标准错误响应(详见 Web层与请求处理)。

建议:枚举取值、取值范围优先用 Pydantic 原生约束(gt/le/Literal)或 field_validator 表达,而不是在 Service 里手写 if 判断,保证错误能落到统一的响应结构。

内容清洗与防注入 ​

富文本 XSS 清洗 ​

sanitize.py 基于 bleach 实现 sanitize_html(),对富文本字段做白名单清洗:仅保留预定义的标签(如 p、div、table、img、a 等)、属性(如 href、style、class)与 CSS 样式,其余脚本、事件处理器、危险标签被剥离(strip=True 并移除注释)。

业务侧在 Schema 的字段校验器里直接串接即可,例如通知公告内容:

python
from app.framework.security.sanitize import sanitize_html

class NoticeCreateSchema(BaseModel):
    content: str = Field(..., max_length=65535, description="公告内容")

    @field_validator("content")
    @classmethod
    def _sanitize_content(cls, value: str) -> str:
        return sanitize_html(value)

前端富文本编辑器(如 Quill/Tiptap)提交的内容,后端一律再清洗一次,遵循「前端不可信」原则。

文件名与路径清洗 ​

文件上传相关的安全校验集中在 framework/file/util.py 的 FileUploadUtil,覆盖:

  • 文件名清洗 sanitize_filename:去除路径分隔符、控制字符,.. 折叠,避免 ../../etc/passwd 类路径穿越。
  • 路径穿越检测 check_path_traversal / sanitize_target_path:拦截 ../、\0 等穿越字符,业务目录前缀统一收敛到安全片段。
  • 扩展名黑名单 is_dangerous_extension / validate_file_extension:依据 settings.DANGEROUS_EXTENSIONS 拒绝可执行脚本等危险类型。
  • 内容魔数校验 detect_file_type:通过文件头签名识别真实类型,与声明扩展名比对(不一致仅告警),防止伪装扩展名上传。
  • 大小限制 check_file_size:依据 settings.MAX_FILE_SIZE 拦截超大文件。

防重放与验证码 ​

图形验证码由 CaptchaUtil(见下文加密工具)生成,登录与短信发送环节结合 Redis 校验,避免接口被暴力刷取。

图形验证码 CaptchaUtil,基于 PIL 生成 base64 PNG 验证码,含字符验证码 generate_captcha(4 位字母数字、干扰线/噪点)与算术验证码 captcha_arithmetic(支持 easy/medium/hard 难度,保证减法结果非负),返回 (图片base64, 答案)。

加密与哈希 ​

security/crypto.py 提供安全工具,密钥与盐均来自 settings,默认不落库明文。

密码哈希 PwdUtil ​

基于 PBKDF2-HMAC-SHA256,迭代次数 60 万、随机 16 字节盐,哈希结果以自定义前缀格式存储:$pbkdf2-sha256$<iterations>$<salt>$<hash>。

  • hash_password(plain):加盐哈希明文密码。
  • verify_password(plain, hashed):解析存量哈希并比对,解析失败安全返回 False。
  • check_password_strength(plain):强度校验(至少 6 位且同时含大小写字母与数字),不合规返回中文提示文案。

用户与 OAuth2 登录流程均通过它完成密码的哈希存储与校验:

python
from app.framework.security.crypto import PwdUtil

# 创建/修改用户
data.password = PwdUtil.hash_password(password=data.password)
# 登录校验
if not PwdUtil.verify_password(plain_password=login_params.password, password_hash=user.password):
    raise CustomException(msg="账号或密码错误")

对称加密 AESCipher ​

基于 cryptography 的 AES-CBC,随机 16 字节 IV 前置拼接在密文,使用 PKCS7 填充,密钥支持 bytes 或十六进制字符串。用于需要可逆加密的敏感字段(如第三方密钥、配置)。加密返回 IV+密文 二进制,解密自动剥离 IV。

摘要 Md5Cipher ​

单向 MD5 摘要(encrypt 返回 32 位小写十六进制)。仅用于签名/去重等非安全敏感场景,敏感凭证请勿使用 MD5。

前端协同要点 ​

  • 时间格式:前端展示/提交日期时间应与后端 constants 中的展示格式一致,避免 T 与空格格式混用;后端 DateTimeStr 等类型已做双向序列化兜底。
  • 富文本:编辑器侧可做基础净化,但最终以 sanitize_html 为准,前端不要假设提交内容原样落库。
  • 文件上传:上传前可按 DANGEROUS_EXTENSIONS、MAX_FILE_SIZE 做前端预检提升体验,但服务端 FileUploadUtil 的校验才是安全边界。
  • 错误提示:校验失败由后端返回统一错误结构,前端表单按字段名回填错误信息即可(详见 前端手册《接口请求与适配层》)。

安全清单 ​

  • 入参一律走 Pydantic Schema 校验,禁止在 Service 中直接信任 request.json()。
  • 富文本、文件必须后端清洗;用户输入做 XSS / 路径穿越防护。
  • 密码用 PwdUtil 加盐哈希,绝不明文存储;可逆敏感字段用 AESCipher。
  • 日志脱敏,验证码/限流防刷,密钥统一来源于 settings。