安全 / 已完成

后端安全加固实战

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

返回文章积累

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

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

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

很多初学者会觉得:

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

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

最少要先守住:

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

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 写 *浏览器页面可能读取不该开放的跨域响应限定真实域名,接口仍做鉴权
日志打印 tokentoken 泄漏敏感字段脱敏
上传文件不限制磁盘或安全风险限大小、类型、存储目录
SQL 拼字符串SQL 注入ORM 或参数化查询

9. 小结

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

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

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