外观
日志与安全
概述
后端在 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。