Level 40 / 阅读练习

后端安全加固实战

理解密码 hash、token、权限校验、CORS、限流、SQL 注入、文件上传和密钥管理。

Progress

阅读中

0/2 步完成 / 阅读后完成练习才算通关

Practice

本关怎么过

  • 读完正文后确认完成阅读
  • 完成 1 道选择题练习
  • 答错时看解释,再回到正文补理解

一句话:后端安全加固就是把常见入口守住,避免用户数据、接口权限、服务器密钥因为低级问题暴露。

本篇学完你会什么:知道上线前要检查哪些安全点,包括密码、token、权限、CORS、限流、SQL 注入、文件上传和密钥管理。

1. 安全不是最后才做的事

很多初学者会觉得:

功能先跑通,安全以后再补。

这句话只对一半。功能可以先做最小版,但安全底线不能空着。

最少要先守住:

安全点 为什么重要
密码不存明文 数据库泄漏时降低伤害
接口必须校验权限 前端隐藏按钮不等于安全
输入必须校验 防止脏数据和攻击输入
密钥不能进仓库 避免 token、数据库被接管
上传文件要限制 防止恶意文件和超大文件
错误信息别暴露细节 不给攻击者提示

<!-- image-slot: backend-security-layers; purpose: 展示登录、权限、输入、上传、配置、日志等后端安全防线; alt: 后端安全加固分层图 -->

2. 登录和密码怎么守

密码不能明文保存

错误做法:

password = "123456"

正确做法:

password_hash = hash("123456")

后端只保存 hash,不保存原始密码。

常见工具:

from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def hash_password(password: str) -> str:
    return pwd_context.hash(password)


def verify_password(password: str, password_hash: str) -> bool:
    return pwd_context.verify(password, password_hash)

登录失败要限流

如果登录接口不限流,别人可以一直猜密码。

建议:

场景 做法
同一个账号连续失败 短时间锁定或验证码
同一个 IP 高频请求 Redis 限流
密码太弱 注册/改密时拒绝
管理员账号 更严格的密码和登录记录

3. token 和权限怎么守

JWT 不是“万能通行证”。它只是证明“这个请求是谁发的”。

后端仍然要做两步:

认证 authentication:你是谁
授权 authorization:你能不能做这件事

例子:

def require_permission(code: str):
    async def checker(current_user = Depends(get_current_user)):
        permissions = await get_user_permissions(current_user.id)
        if code not in permissions:
            raise HTTPException(status_code=403, detail="Forbidden")
        return current_user

    return checker

前端隐藏按钮只是体验,真正的安全必须在后端接口上。

token 还要注意:

问题 建议
token 过期时间太长 access token 短一点,refresh token 单独处理
token 泄漏 HTTPS、不要写进日志
权限变化后 token 仍有效 服务端查权限或使用 token version
退出登录 客户端删除 token,必要时服务端黑名单

4. 输入、SQL 和文件上传怎么守

输入校验

所有进入系统的数据都不应该默认相信。

Pydantic 可以先挡住很多问题:

from pydantic import BaseModel, EmailStr, Field


class UserCreate(BaseModel):
    username: str = Field(min_length=3, max_length=32, pattern=r"^[a-zA-Z0-9_]+$")
    email: EmailStr
    role_ids: list[int] = Field(default_factory=list)

EmailStr 会检查邮箱格式。如果项目里还没有安装邮箱校验依赖,需要补上 email-validator。真实项目里还要确认这个邮箱是否已经被占用,以及是否需要邮箱验证流程。

校验要做在入口处,避免脏数据进入 service 和数据库。

SQL 注入

危险写法:

sql = f"SELECT * FROM users WHERE username = '{username}'"

安全写法:

stmt = select(User).where(User.username == username)

使用 ORM 或参数化查询,不要拼接用户输入。

文件上传

文件上传要限制:

限制 为什么
文件大小 防止占满磁盘
文件类型 防止上传危险脚本
文件名重命名 防止覆盖和路径攻击
存储位置 不要直接放进可执行目录
病毒/内容扫描 重要业务需要

5. CORS、限流和错误信息怎么守

CORS

开发环境可以宽一点:

http://localhost:3000
http://localhost:5173

生产环境不建议直接写:

*

应该明确允许真实前端域名,例如:

https://admin.example.com
https://www.example.com

这里也容易误解:CORS 不是登录鉴权,它主要管“浏览器里的网页能不能读取跨域响应”。就算 CORS 配好了,后端接口仍然必须校验 token、权限和必要的 CSRF 防护。

场景 建议
管理后台接口 只允许后台真实域名
使用 Cookie 登录 不要用 *,并正确配置 credentials
公开只读接口 可以更宽,但仍要考虑限流
服务端调用服务端 CORS 不负责这类访问控制

限流

这些接口建议限流:

接口 原因
登录 防爆破
发送验证码 防短信/邮件刷量
文件上传 防资源耗尽
AI 生成接口 防成本失控
搜索接口 防数据库压力

错误信息

不要把内部错误直接返回给用户:

数据库连接失败:postgresql://user:password@...

对外返回:

{
  "message": "服务暂时不可用,请稍后再试"
}

详细堆栈放日志,不放响应体。

6. 密钥和配置怎么守

这些东西不要提交到仓库:

JWT_SECRET
DATABASE_URL
REDIS_PASSWORD
OPENAI_API_KEY
对象存储密钥
短信服务密钥

推荐做法:

环境 做法
本地 .env,不提交
示例 .env.example,只放字段名和假值
测试 CI/CD secret 或服务器环境变量
生产 密钥管理服务或服务器环境变量

还要定期检查:

git grep -n "sk-" .
git grep -n "JWT_SECRET" .
git grep -n "password=" .

这不是完整扫描工具,但能先发现明显泄漏。

7. 上线前安全检查

[ ] 密码只保存 hash
[ ] 登录失败有限流
[ ] 管理接口有后端权限校验
[ ] CORS 在生产只允许真实前端域名
[ ] SQL 没有拼接用户输入
[ ] 文件上传限制大小和类型
[ ] 错误响应不暴露堆栈和密钥
[ ] token 不打印进日志
[ ] .env 不提交
[ ] 生产密钥不是默认值

8. 常见错误

错误 后果 修正
只做前端权限 接口可被直接调用 后端每个敏感接口都校验
JWT_SECRET 使用默认值 token 可被伪造 使用强随机密钥
生产 CORS 写 * 浏览器页面可能读取不该开放的跨域响应 限定真实域名,接口仍做鉴权
日志打印 token token 泄漏 敏感字段脱敏
上传文件不限制 磁盘或安全风险 限大小、类型、存储目录
SQL 拼字符串 SQL 注入 ORM 或参数化查询

9. 小结

名词 大白话
认证 确认你是谁
授权 确认你能不能做这件事
hash 密码的不可逆保存结果
CORS 浏览器跨域访问规则
限流 控制接口访问频率
密钥 系统的钥匙,不该进仓库

上一篇建议:先看《CI/CD、发布和回滚》。

下一篇建议:继续看《数据库备份、迁移和恢复》,补上数据安全。

Checkpoint

读完后做题闯关

读完后做选择题检验理解,答错会显示解释。

0/1
先完成阅读

读完正文后点击“我已读完,进入练习”,题目会变成可答状态。

第 1 题

读完这一篇后,最适合怎么确认自己真的理解了?