如何限制网站仅允许已认证IP地址访问
嘿,这个需求在SaaS场景里太常见了,我来给你拆解成清晰的步骤,帮你快速落地,不用慌!
整体核心思路
你的需求本质是双重访问控制:先确认请求来自「已认证的系统」,再校验请求的IP在数据库维护的白名单内。我们要把这个校验逻辑放在请求到达业务代码之前的拦截层(比如后端中间件或者API网关),这样能最大程度减少无效请求对业务的影响。
具体实现步骤拆解
1. 先搞定数据库表设计
首先需要一张IP白名单表,用来存储允许访问的IP,建议关联到你的「已认证系统」表(比如每个系统可以绑定多个IP):
CREATE TABLE ip_whitelist ( id INT AUTO_INCREMENT PRIMARY KEY, allowed_ip VARCHAR(45) NOT NULL UNIQUE, -- 支持IPv4/IPv6,IPv6最长是39字符,45足够 system_id INT NOT NULL, -- 关联你的已认证系统表ID description VARCHAR(255) DEFAULT '', -- 可选:备注这个IP对应的系统用途 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (system_id) REFERENCES authenticated_systems(id) );
如果你的场景是全局IP白名单(只要IP在名单里就允许已认证系统访问),也可以简化表结构,去掉system_id,直接存允许的IP即可。
2. 关键:获取请求的真实IP
这里要注意!如果你的SaaS应用部署在反向代理(比如Nginx、Cloudflare)后面,直接取请求的原始IP会拿到代理服务器的IP,而不是真实客户端的IP。所以要从请求头里取真实IP:
- 常用的请求头:
X-Forwarded-For、X-Real-IP - 注意:如果有多层代理,
X-Forwarded-For是逗号分隔的IP列表,第一个就是真实客户端IP
3. 编写请求拦截校验逻辑
把校验逻辑做成全局中间件(所有请求都要经过),步骤如下:
- 第一步:提取请求的真实IP
- 第二步:验证请求的系统身份(比如校验请求头里的
Authorization: Bearer <API_KEY>,查你的已认证系统表确认密钥有效) - 第三步:根据已认证的
system_id,查询ip_whitelist表,看该IP是否在这个系统的允许列表内 - 第四步:如果校验不通过,直接返回
403 Forbidden,拒绝后续请求
4. 性能优化(可选但推荐)
如果你的请求量很大,每次校验都查数据库会有性能瓶颈,建议把IP白名单缓存到Redis里:
- 定时从数据库同步白名单到Redis(比如每分钟同步一次)
- 校验时先查Redis,不存在再查数据库,提升响应速度
代码示例(两种常见后端框架)
Node.js + Express 示例
const express = require('express'); const mysql = require('mysql2/promise'); const app = express(); // 数据库连接配置(实际项目建议用连接池) const dbConfig = { host: 'your-db-host', user: 'your-db-user', password: 'your-db-pass', database: 'your-db-name' }; // 全局中间件:IP白名单校验 app.use(async (req, res, next) => { try { // 1. 获取真实IP const realIp = req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.headers['x-real-ip']?.trim() || req.ip; if (!realIp) { return res.status(403).json({ error: '无法获取请求IP' }); } // 2. 校验系统身份(这里示例用API_KEY,实际根据你的认证方式调整) const apiKey = req.headers['authorization']?.split(' ')[1]; if (!apiKey) { return res.status(401).json({ error: '缺少系统认证信息' }); } const connection = await mysql.createConnection(dbConfig); // 查已认证系统 const [systems] = await connection.execute('SELECT id FROM authenticated_systems WHERE api_key = ?', [apiKey]); if (systems.length === 0) { await connection.end(); return res.status(401).json({ error: '无效的系统认证信息' }); } const systemId = systems[0].id; // 3. 校验IP是否在白名单内 const [whitelist] = await connection.execute('SELECT * FROM ip_whitelist WHERE allowed_ip = ? AND system_id = ?', [realIp, systemId]); await connection.end(); if (whitelist.length === 0) { return res.status(403).json({ error: 'IP不在允许访问列表内' }); } // 校验通过,继续处理请求 next(); } catch (err) { console.error('校验出错:', err); return res.status(500).json({ error: '服务器内部错误' }); } }); // 你的业务路由 app.get('/api/your-service', (req, res) => { res.json({ message: '访问成功!' }); }); app.listen(3000, () => { console.log('服务器启动在3000端口'); });
Python + Django 示例
先自定义中间件ip_whitelist_middleware.py:
from django.http import JsonResponse from django.db import connection from django.utils.deprecation import MiddlewareMixin class IPWhitelistMiddleware(MiddlewareMixin): def process_request(self, request): # 1. 获取真实IP def get_real_ip(req): x_forwarded_for = req.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: return x_forwarded_for.split(',')[0].strip() return req.META.get('HTTP_X_REAL_IP') or req.META.get('REMOTE_ADDR') real_ip = get_real_ip(request) if not real_ip: return JsonResponse({'error': '无法获取请求IP'}, status=403) # 2. 校验系统身份(示例用API_KEY,实际调整) api_key = request.META.get('HTTP_AUTHORIZATION', '').split(' ')[-1] if not api_key: return JsonResponse({'error': '缺少系统认证信息'}, status=401) # 查询已认证系统 with connection.cursor() as cursor: cursor.execute("SELECT id FROM authenticated_systems WHERE api_key = %s", [api_key]) system = cursor.fetchone() if not system: return JsonResponse({'error': '无效的系统认证信息'}, status=401) system_id = system[0] # 3. 校验IP白名单 cursor.execute("SELECT * FROM ip_whitelist WHERE allowed_ip = %s AND system_id = %s", [real_ip, system_id]) whitelist_entry = cursor.fetchone() if not whitelist_entry: return JsonResponse({'error': 'IP不在允许访问列表内'}, status=403) # 校验通过,继续处理 return None
然后在settings.py里注册中间件:
MIDDLEWARE = [ # ... 其他中间件 'your_app.ip_whitelist_middleware.IPWhitelistMiddleware', ]
额外注意点
- 如果需要支持CIDR网段(比如允许
192.168.1.0/24整个网段),可以用数据库的IP范围查询,或者在代码里用专门的IP处理库(比如Node.js的ip库,Python的ipaddress模块)来判断IP是否在网段内。 - 一定要做好日志记录:记录每个被拒绝的请求的IP、系统认证信息、时间,方便后续排查问题。
内容的提问来源于stack exchange,提问作者Gans
相关产品推荐
相关产品推荐

