Laravel禁止API路由浏览器直接访问时前端调用报403如何解决
你当前使用的$request->ajax()判断逻辑,本质是校验请求是否携带X-Requested-With: XMLHttpRequest请求头,这套逻辑失效的原因很明确:
- 跨域请求场景下,axios默认不会自动携带这个自定义请求头,没有手动配置的情况下请求会被直接拦截
- Next.js存在服务端渲染逻辑,服务端侧发起的API请求不属于浏览器XHR/fetch范畴,天然不会携带这个头,同样会被拦截
- 最核心的缺陷是:这个请求头可以被任意客户端手动伪造,Postman、脚本爬虫只要加个对应头就能绕过校验,完全达不到“仅允许自有站点调用”的安全要求,生产环境绝对不能依赖这个逻辑做防护。
如果只是临时调试让自己的站点能通,做两步配置即可:
- 全局配置axios,给所有请求自动添加所需请求头:
// 你的axios全局配置位置 axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
- Next.js服务端侧(比如
getServerSideProps、服务端组件中)发起的请求,手动在headers配置里加上上述请求头。
再次提醒:这个方案没有任何实际防护能力,任何人只要在请求里加个对应头就能访问你的API,绝对不要在生产环境用。
首先明确一个前提:没有任何方案能100%阻止抓包后伪造请求的行为,我们能做的是提升恶意调用成本,同时拦截普通用户直接在地址栏访问API的行为,按实现成本和安全性排序推荐以下方案:
方案1:校验请求来源头(实现成本最低,满足基础防护需求)
浏览器直接地址栏访问API时不会携带合法的来源标识,第三方站点跨域发起的请求来源域名也和你的自有站点不匹配,可以把中间件逻辑改成校验Origin/Referer头:
public function handle($request, \Closure $next) { // 替换为你自己的允许域名,本地开发环境按需添加 $allowedOrigins = [ 'https://your-site.com', 'http://localhost:3000', // Next.js本地开发默认端口 ]; $origin = $request->headers->get('origin'); $referer = $request->headers->get('referer'); $pass = false; // 优先校验Origin头 if ($origin && in_array($origin, $allowedOrigins)) { $pass = true; } // 部分同域GET请求可能不带Origin,降级校验Referer头 if (!$pass && $referer) { $refererParts = parse_url($referer); $refererOrigin = $refererParts['scheme'] . '://' . $refererParts['host']; if (isset($refererParts['port'])) { $refererOrigin .= ':' . $refererParts['port']; } if (in_array($refererOrigin, $allowedOrigins)) { $pass = true; } } if (!$pass) { return response('Forbidden.', 403); } return $next($request); }
配合Laravel的CORS配置,把config/cors.php中的allowed_origins严格设置为上述自有域名列表,不要使用*通配符。
这个方案的效果:
- 普通用户直接在浏览器地址栏输入API地址,请求无合法来源头,会被直接拦截
- 第三方站点页面发起的跨域请求,来源域名不匹配会被拦截
- 自有站点不管是客户端侧axios请求,还是部署在同域名下的服务端请求,都能正常通过
- 注意:纯服务端发起、不经过用户浏览器的请求,需要手动配置Origin头为允许列表内的域名,否则会被拦截。
方案2:请求签名校验(安全性高,适合敏感接口场景)
如果接口涉及敏感数据,仅靠来源头校验不够(非浏览器客户端可以随意伪造来源头),可以给所有API请求增加签名逻辑:
- 前后端约定一个不对外公开的签名密钥
- 前端发起请求时,将当前时间戳、请求参数按固定规则排序后拼接密钥,生成哈希签名,把时间戳和签名放到请求头中传递
- 后端中间件用相同规则生成签名做比对,同时校验时间戳是否在允许的时间窗口内(比如5分钟,防止重放攻击),校验不通过直接返回403
这套方案下,攻击者就算抓包拿到接口地址,不知道签名密钥也无法伪造合法请求,安全性远高于头校验。
方案3:接口鉴权(通用标准方案)
如果你的API需要用户登录后才能访问,直接使用Laravel官方提供的Sanctum或Passport做接口鉴权即可,没有携带合法登录凭证的请求,不管是直接地址栏访问还是第三方调用,统一返回401状态码。这是商业项目最通用的API防护方案,可靠性远高于自定义的客户端类型判断逻辑。
注意:不要把“拦截浏览器直接访问”作为安全防护的核心目标,所有HTTP请求的特征都可以被伪造,API安全的核心永远是身份校验和权限控制,而非判断请求发起的客户端类型。
内容的提问来源于stack exchange,提问作者Jack The Baker

