feat(security): 添加fail2ban集成防止暴力破解登录

- 新增fail2ban过滤器和监狱配置文件
- 修改登录视图记录客户端IP和认证失败日志
- 更新日志配置添加syslog处理器用于fail2ban检测
- 在README中添加fail2ban配置和使用文档
This commit is contained in:
2026-01-28 22:44:36 +08:00
parent f7692a6db6
commit ae95844177
5 changed files with 411 additions and 9 deletions

283
README.md
View File

@@ -1625,6 +1625,287 @@ print('初始化完成')
**解决方法**确认使用的是POST方法不是GET方法
## Fail2ban 登录保护配置
为了防止暴力破解登录密码,系统集成了 Fail2ban 自动封禁功能。当用户在短时间内多次登录失败时,其 IP 地址将被自动封禁。
### 工作原理
1. **日志记录**:登录失败时,系统会记录包含 IP 地址的警告日志到 syslog 和 `logs/auth.log`
2. **Fail2ban 监控**Fail2ban 实时监控日志文件,检测登录失败事件
3. **自动封禁**:当同一 IP 在指定时间内失败次数超过阈值时,自动封禁该 IP
### 安装 Fail2ban
在 Ubuntu 服务器上安装 Fail2ban
```bash
# 更新包管理器
sudo apt update
# 安装 Fail2ban
sudo apt install fail2ban -y
# 启动 Fail2ban 服务
sudo systemctl start fail2ban
# 设置开机自启
sudo systemctl enable fail2ban
# 检查服务状态
sudo systemctl status fail2ban
```
### 配置 Fail2ban
#### 1. 复制过滤器配置
```bash
# 创建过滤器目录(如果不存在)
sudo mkdir -p /etc/fail2ban/filter.d
# 复制过滤器配置
sudo cp /var/www/diary-family/deploy/fail2ban/filter.d/diary-family.conf /etc/fail2ban/filter.d/
# 验证过滤器配置
sudo fail2ban-client -t
```
#### 2. 复制监狱配置
```bash
# 创建监狱配置目录(如果不存在)
sudo mkdir -p /etc/fail2ban/jail.d
# 复制监狱配置
sudo cp /var/www/diary-family/deploy/fail2ban/jail.d/diary-family.conf /etc/fail2ban/jail.d/
# 编辑配置(根据实际需求修改)
sudo nano /etc/fail2ban/jail.d/diary-family.conf
```
#### 3. 关键配置参数说明
| 参数 | 默认值 | 说明 |
|-----|-------|------|
| `maxretry` | 5 | 触发封禁前的最大失败次数 |
| `bantime` | 3600 | 封禁时间默认1小时 |
| `findtime` | 600 | 检测时间窗口默认10分钟 |
| `ignoreip` | 127.0.0.1 | 白名单IP不会被封禁 |
#### 4. 重启 Fail2ban 服务
```bash
# 重新加载配置
sudo systemctl restart fail2ban
# 或者使用 fail2ban-client
sudo fail2ban-client reload
```
### 验证配置
#### 1. 检查 Fail2ban 状态
```bash
# 查看整体状态
sudo fail2ban-client status
# 查看 diary-family 监狱状态
sudo fail2ban-client status diary-family
# 查看被封禁的IP列表
sudo fail2ban-client status diary-family | grep "Banned IP list"
```
#### 2. 测试登录失败检测
```bash
# 手动测试过滤器(使用最近的日志)
sudo fail2ban-regex /var/log/syslog /etc/fail2ban/filter.d/diary-family.conf
# 或者测试 auth.log
sudo fail2ban-regex /var/www/diary-family/logs/auth.log /etc/fail2ban/filter.d/diary-family.conf
```
#### 3. 查看日志
```bash
# 查看 Fail2ban 日志
sudo tail -f /var/log/fail2ban.log
# 查看系统日志中的认证失败
sudo grep "Authentication failure" /var/log/syslog
# 查看 Django 认证日志
tail -f /var/www/diary-family/logs/auth.log
```
### 手动管理封禁
#### 解封 IP 地址
```bash
# 手动解封某个 IP
sudo fail2ban-client set diary-family unbanip 192.168.1.100
# 解封所有 IP
sudo fail2ban-client set diary-family unbanip --all
```
#### 封禁 IP 地址
```bash
# 手动封禁某个 IP
sudo fail2ban-client set diary-family banip 192.168.1.100
```
### 高级配置
#### 使用 UFW 作为防火墙后端
如果使用 UFW 防火墙,修改配置:
```bash
# 编辑监狱配置
sudo nano /etc/fail2ban/jail.d/diary-family.conf
# 修改 banaction
banaction = ufw
# 重启服务
sudo systemctl restart fail2ban
```
#### 配置邮件通知
启用邮件通知功能:
```bash
# 编辑监狱配置
sudo nano /etc/fail2ban/jail.d/diary-family.conf
# 添加邮件配置
destemail = admin@example.com
sender = fail2ban@example.com
mta = sendmail
action = %(action_mwl)s
# 重启服务
sudo systemctl restart fail2ban
```
#### 调整封禁策略
编辑 `/etc/fail2ban/jail.d/diary-family.conf`
```ini
# 更严格的策略3次失败封禁24小时
maxretry = 3
bantime = 86400
findtime = 300
# 或者更宽松的策略10次失败封禁30分钟
maxretry = 10
bantime = 1800
findtime = 900
```
### 故障排除
#### 问题1Fail2ban 无法启动
```bash
# 检查配置文件语法
sudo fail2ban-client -t
# 查看详细错误信息
sudo journalctl -u fail2ban -f
# 检查日志文件权限
ls -la /var/log/syslog
ls -la /var/www/diary-family/logs/auth.log
```
#### 问题2无法检测到登录失败
```bash
# 检查日志格式是否匹配
sudo fail2ban-regex /var/log/syslog /etc/fail2ban/filter.d/diary-family.conf
# 手动检查日志内容
grep "Authentication failure" /var/log/syslog
grep "Authentication failure" /var/www/diary-family/logs/auth.log
# 检查 Django 是否正确记录日志
tail -f /var/www/diary-family/logs/auth.log
```
#### 问题3IP 未被封禁
```bash
# 检查 iptables 规则
sudo iptables -L -n | grep fail2ban
# 检查封禁状态
sudo fail2ban-client status diary-family
# 检查日志中是否有封禁动作
sudo grep "Ban" /var/log/fail2ban.log
```
#### 问题4syslog 无法写入
如果使用 syslog 出现权限问题,可以只使用文件日志:
```bash
# 编辑监狱配置,只监控 auth.log
sudo nano /etc/fail2ban/jail.d/diary-family.conf
# 修改 logpath
logpath = /var/www/diary-family/logs/auth.log
# 重启服务
sudo systemctl restart fail2ban
```
### 安全建议
1. **合理设置阈值**:根据实际需求调整 `maxretry` 和 `findtime`,避免误封正常用户
2. **配置白名单**:将办公网络、家庭网络等添加到 `ignoreip`
3. **定期检查日志**:定期查看 `/var/log/fail2ban.log` 了解封禁情况
4. **设置永久封禁**:对于频繁攻击的 IP可以手动永久封禁或设置很长的 `bantime`
5. **监控异常**:结合邮件通知,及时了解安全事件
### 与 Nginx 集成(可选)
如果使用 Nginx 作为反向代理,可以额外配置 Nginx 日志监控:
```bash
# 创建 Nginx 过滤器
sudo tee /etc/fail2ban/filter.d/nginx-diary-family.conf << 'EOF'
[Definition]
failregex = ^<HOST> .* "POST /login/ HTTP/.*" (401|403|500)
^<HOST> .* "POST /login/ HTTP/.*" 200 .* "Invalid login"
ignoreregex =
EOF
# 添加到监狱配置
sudo tee -a /etc/fail2ban/jail.d/diary-family.conf << 'EOF'
[nginx-diary-family]
enabled = true
filter = nginx-diary-family
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 3600
findtime = 600
EOF
# 重启服务
sudo systemctl restart fail2ban
```
## 注意事项
1. 定期备份数据库
@@ -1632,6 +1913,8 @@ print('初始化完成')
3. 定期清理过期数据
4. 保持依赖包更新
5. 定期运行邮件测试脚本,确保邮件功能正常
6. **Fail2ban 配置**:生产环境务必配置 Fail2ban 防止暴力破解
7. **白名单设置**:配置 `ignoreip` 避免误封自己的 IP
## 许可证

View File

@@ -10,6 +10,7 @@ from django.contrib.auth.decorators import login_required
from django.contrib import messages
from datetime import timedelta, datetime
import os
import logging
from loguru import logger
# WeasyPrint可用性标记初始为None
@@ -889,6 +890,9 @@ def api_submit_summary(request):
logger.error(f"API: 提交汇总记录失败: {str(e)}")
return JsonResponse({'success': False, 'message': f"提交失败: {str(e)}"}, status=500)
# 获取syslog日志记录器用于fail2ban检测
syslog_logger = logging.getLogger('django.security.login')
# 登录视图
def user_login(request):
"""用户登录"""
@@ -900,17 +904,26 @@ def user_login(request):
username = request.POST.get('username')
password = request.POST.get('password')
logger.info(f"用户登录尝试: {username}")
# 获取客户端IP地址
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
client_ip = x_forwarded_for.split(',')[0].strip()
else:
client_ip = request.META.get('REMOTE_ADDR', 'unknown')
logger.info(f"用户登录尝试: {username}, IP: {client_ip}")
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
logger.info(f"用户 {username} 登录成功")
logger.info(f"用户 {username} 登录成功, IP: {client_ip}")
messages.success(request, '登录成功!')
return redirect('index')
else:
logger.warning(f"用户 {username} 登录失败: 用户名或密码错误")
logger.warning(f"用户 {username} 登录失败: 用户名或密码错误, IP: {client_ip}")
# 记录到syslog供fail2ban检测
syslog_logger.warning(f"Authentication failure for username: {username} from IP: {client_ip}")
messages.error(request, '用户名或密码错误,请重新尝试。')
return render(request, 'core/login.html')

View File

@@ -0,0 +1,20 @@
# Fail2Ban filter for diary-family Django application
# 用于检测家庭日报系统登录失败的规则
[Definition]
# 匹配登录失败的日志行
# 日志格式: django.security.login: WARNING Authentication failure for username: xxx from IP: xxx.xxx.xxx.xxx
failregex = ^.*Authentication failure for username: .* from IP: <HOST>.*$
# 可选:匹配其他认证失败模式(如被禁用的用户)
# failregex = ^%(__prefix_line)s.*Authentication failure for username: .* from IP: <HOST>.*$
# ^%(__prefix_line)s.*Invalid login attempt from IP: <HOST>.*$
# 忽略正则(可选)
# ignoreregex =
[Init]
# 日期格式(如果需要)
# datepattern = %%Y-%%m-%%d %%H:%%M:%%S

View File

@@ -0,0 +1,62 @@
# Fail2Ban jail configuration for diary-family Django application
# 家庭日报系统 fail2ban 监狱配置
[diary-family]
# 启用该监狱
enabled = true
# 监狱名称和过滤器
filter = diary-family
# 要监控的日志文件路径
# 如果使用syslog可以设置为 /var/log/syslog 或 /var/log/messages
# 如果使用文件日志,设置为 Django 项目的 auth.log 路径
logpath = /var/log/syslog
/var/www/diary-family/logs/auth.log
# 触发封禁前的最大失败次数
maxretry = 5
# 封禁时间(秒)- 默认1小时3600秒
# 可以设置为:
# - 3600 = 1小时
# - 86400 = 1天
# - 604800 = 1周
# - -1 = 永久封禁
bantime = 3600
# 检测时间窗口(秒)- 在多长时间内计算失败次数
# 例如findtime = 600 表示在10分钟内失败 maxretry 次则封禁
findtime = 600
# 忽略的IP地址白名单
# 可以添加本地网络、办公网络等不需要封禁的IP
ignoreip = 127.0.0.1/8 ::1
# 192.168.1.0/24 # 本地网络示例
# 10.0.0.0/8 # 内网示例
# 使用的封禁动作
# 默认使用 iptables 封禁所有端口
# 可选动作:
# - iptables-allports: 封禁所有端口
# - iptables-multiport: 封禁指定端口
# - nftables-allports: 使用 nftables 封禁
# - ufw: 使用 UFW 防火墙
# - cloudflare: 封禁 Cloudflare 上的IP
banaction = iptables-allports
# 发送邮件通知(可选)
# 需要配置邮件服务器
# destemail = admin@example.com
# sender = fail2ban@example.com
# mta = sendmail
# action = %(action_mwl)s # 发送邮件并记录日志
# 后端选择
# - auto: 自动选择(推荐)
# - systemd: 使用 systemd journal
# - polling: 轮询模式
backend = auto
# 端口(用于 multiport 动作)
port = 80,443,8000

View File

@@ -196,6 +196,9 @@ LOGGING = {
'format': '[%(asctime)s] [%(levelname)s] [%(process)d] [%(module)s] %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'
},
'syslog': { # syslog格式用于fail2ban检测
'format': '%(name)s: %(levelname)s %(message)s'
},
},
'handlers': {
'file': { # 日志写入文件的处理器
@@ -213,6 +216,22 @@ LOGGING = {
'class': 'logging.StreamHandler',
'formatter': 'standard'
},
'syslog': { # syslog处理器用于fail2ban检测登录失败
'level': 'WARNING',
'class': 'logging.handlers.SysLogHandler',
'address': '/dev/log', # Linux系统日志socket
'facility': 'local0',
'formatter': 'syslog',
},
'auth_file': { # 认证日志文件处理器(备选方案)
'level': 'WARNING',
'class': 'logging.handlers.RotatingFileHandler',
'filename': str(LOG_DIR / 'auth.log'),
'maxBytes': 1024 * 1024 * 50, # 50MB
'backupCount': 5,
'formatter': 'standard',
'encoding': 'utf-8',
},
},
# 所有日志器配置和原配置完全一致,无需任何修改
'loggers': {
@@ -226,6 +245,11 @@ LOGGING = {
'level': 'INFO',
'propagate': True,
},
'django.security.login': { # 登录安全日志用于fail2ban
'handlers': ['syslog', 'auth_file'],
'level': 'WARNING',
'propagate': False,
},
'celery': { # Celery客户端日志Django中提交任务的日志
'handlers': ['file'],
'level': 'INFO',