限制JS接口安全域名访问次数,不能只靠前端Referer校验,必须由服务端统一鉴权、签名、限流三层配合,才算真正落地。很多团队在开发调试阶段用window.location.host或document.referrer做判断,上线后才发现攻击者用curl或Postman就能绕过,等于白做,下面这套思路,能帮你把“限制域名访问次数”这件事从摆设变成真防线。
为什么前端域名校验总被绕过
浏览器发起Ajax请求时,会自动带上Referer字段,后端读取这个字段判断来源是否在白名单内,这是最直观的“JS接口安全域名怎么设置”答案,但问题在于,Referer本质是客户端主动提交的请求头,任何能自定义请求头的工具都能随意伪造,比如攻击者用Python的requests库,写一行headers={"Referer": "https://your-valid-domain.com"},你的接口就放行了。
更麻烦的是,部分浏览器隐私模式或安全插件会主动清空Referer,导致真实用户被误伤,行业共识认为,前端域名校验只适合做“降低误调用概率”的弱防护,不能作为安全边界,真正的限制必须发生在服务端,而且要走“校验来源 + 校验身份 + 校验频率”的组合拳。
服务端校验才是限制JS接口安全域名的核心
既然前端不可信,那就把防线后移,服务端要做的事有三件:校验请求头里的Origin或Referer是否合法、校验请求体里的签名是否匹配、按IP或Token维度限制单位时间内的调用次数,三者缺一不可。
Referer校验:第一道防线的配置要点
Nginx层面可以直接做Referer过滤,这里给出一个常见配置片段(适用于多数LNMP环境):
location /api/ {
valid_referers none blocked server_names
.yourdomain.com
yourdomain.com;
if ($invalid_referer) {
return 403;
}
}
valid_referers后面的参数含义分别是:none表示允许无Referer请求(谨慎开启),blocked表示允许被浏览器过滤但参数完整的请求,.yourdomain.com是你自己的域名前缀匹配。配好之后,务必用在线接口测试工具模拟伪造Referer访问一次,如果返回403,说明Nginx层生效了。
但这里有个隐藏坑:CORS跨域请求时,浏览器会先发送OPTIONS预检请求,Nginx层面如果没放行OPTIONS,跨域调用直接被拦死,所以配置里需要加一段:

if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin ;
add_header Access-Control-Allow-Headers ;
return 204;
}
这段代码只解决预检,真正的安全判断仍然放在业务层。
js防刷接口方案对比:sign签名和token校验哪个更可靠
这是“js防刷接口方案对比”里最常被问到的点,直接给结论:签名更适合开放API,Token更适合登录态接口,实际项目两者经常同时用。
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Referer校验 | 检查请求来源域名 | 配置简单 | 可伪造,仅防小白 | 静态资源防盗链 |
| sign签名 | 参数+密钥拼接后哈希 | 不可伪造,防篡改 | 密钥管理复杂 | 对外开放API |
| 动态Token | 登录后下发放行凭证 | 可单独吊销 | 需要会话管理 | 内部业务接口 |
| IP频率限制 | 统计单位时间请求数 | 实现成本低 | 企业内网用户会误伤 | 防扫描、防刷量 |
sign签名的核心逻辑不复杂:前后端约定一个密钥(私钥),前端把请求参数按字典序排列,加上时间戳拼接成字符串,做MD5或HMAC-SHA256得到sign字段,服务端收到请求后,用同样的算法重新计算,比对结果就完事。只要密钥不泄露,攻击者无法伪造签名,即使他知道了访问域名,没有密钥也生成不了合法请求。
要注意的是时间戳必须参与签名,并且服务端要校验时间戳与当前时间差,业内专家指出,有效时间窗口建议控制在120秒以内,超过就直接拒绝,这一步能挡住大部分重放攻击。
动态Token的坑在于Session分布式环境,如果项目部署了多台服务器,Token要存Redis,不能用服务器本地内存,你可以把Token设置成短时效(比如30分钟),配合refreshToken机制保证用户无感续期,这样既安全又照顾体验。
频控与黑名单:对付定时刷量的实用手段
签名能防伪造,但挡不住“合法签名被批量调用”,比如前端页面被恶意用户开了10个标签页,每一个都拿着真实Token在刷接口,签名校验全部通过,这时候只能靠频率限制兜底。

频控的常规做法是滑动窗口计数,用Redis的INCR和EXPIRE两个命令就能实现,伪代码如下:
key = "rate:" + user_id
count = redis.incr(key)
if count == 1:
redis.expire(key, 60)
if count > 100:
return "请求过于频繁"
单位时间内的阈值要根据业务调,用户点击“保存”类的接口,每分钟30次足够;如果是“刷新Token”接口,每分钟5次都算多。判定规则里一定要区分用户维度(Token)和IP维度,两者独立计数,任何一个超限都拦截。
黑名单机制则是辅助手段,比如某IP连续5次签名校验失败,直接把该IP拉入黑名单24小时,期间所有请求返回404,不给任何业务提示,这里有个技巧,黑名单别用数据库表存,用Redis SET带上过期时间,性能好而且不用手动清理。
从nginx到业务代码:限制访问次数的完整链路
实际操作时不能只依赖某一层,要把Nginx的IP限制、业务代码的Referer校验、签名校验、频控串成一条完整的链,每一层拦截掉一部分异常流量,剩下的才会到达真正的业务逻辑。
参考的部署顺序如下:
- Nginx层按IP限流:用
limit_req_zone模块控制普通接口的IP并发数,超出直接返回503,这个模块的参数在http块里定义,server块里调用。 - 应用层Referer校验:Nginx的
valid_referers有时会把合法的跳转来源误杀,所以在应用层再做一次白名单匹配,这次的白名单是完整域名列表,建议保留不带www的根域名和带www的二级域名。 - 统一入口做sign校验:写一个全局中间件或拦截器,对所有非白名单接口的POST请求验签,验签失败返回自定义错误码,不要返回200,否则攻击者容易判断你的校验逻辑。
- 业务代码里做Token鉴权和频控:校验Token有效性,再走上面的Redis计数逻辑,全部通过才执行真实业务。
这一套流程走下来,即使攻击者从DevTools里抓到了接口地址,也会被卡在第二步或第三步,你可能觉得步骤多,但多数中大型项目的接口安全网关就是这么搭的,拆解看并不神秘。

动态域名白名单:管理后台的加分项
如果你的产品有“允许特定客户域名调用接口”的需求,可以把白名单从配置文件挪到管理后台,具体做法是设计两张表,一张存客户信息,一张存该客户允许绑定的域名列表,每次请求到来时,服务端从缓存中读取该客户的允许域名集合,再跟当前请求的Origin或Referer做精确匹配。
这种做法解决了一个很实际的场景:客户自己改了前端部署地址,但忘了报备,导致线上接口全挂,有了后台就能随时增删域名,不需要改代码发版。缓存建议设置5分钟左右的过期时间,既保证更新及时,又避免每次请求穿透到数据库。
至于“js接口安全域名访问次数设置”的具体数值,没有统一标准,小规模内部系统,单IP每分钟60次够用;面向公网的活动H5页面,单IP每分钟20次正常,签到抽奖接口甚至要压到每分钟5次,建议你先用日志统计一周的调用量分布,再定阈值,比直接拍脑袋靠谱。
常见问题速答
限制JS接口安全域名访问次数时,要不要把请求方式限定为POST?
要,GET请求的参数会暴露在URL里,不仅容易被日志记录导致签名泄露,浏览器地址栏直接访问也很方便,统一改用POST,至少能让攻击者多一步构造请求体的操作。
前端上报Referer为空怎么办?
如果是自家产品,建议要求前端所有请求都显式设置X-Requested-With: XMLHttpRequest头,后端校验这个头是否等于预期值,浏览器跨域场景下这个头不可随意伪造,比Referer稳定得多,如果确实需要兼容某些特殊浏览器,可以把考试校验换成“允许空Referer但必须携带有效Token”。
小程序环境的域名限制怎么做?
小程序的JS接口不叫“域名”,而叫“request合法域名”,配置在微信公众平台后台,小程序的权限校验比网页严格,因为它强制要求HTTPS,而且域名不能带端口,做请求时统一从wx.request封装一个函数,把header里的Referer全部带走,后端校验逻辑不变,只是来源域名变成了servicewechat.com相关的固定前缀,需要提醒的是,小程序后台配置合法域名后不是即时生效,有十几分钟的缓存,调试时要留意。