Laravel5 使用 fetch API 进行 ajax 的POST访问出现 TokenMismatchException

发布于 2022-09-04 13:23:45 字数 1316 浏览 34 评论 0

最近在学习Laravel 5。

使用laravel new命令创建了一个新项目。

建了一个测试的 Controller,在/app/Http/Controllers下,完整代码:

<?php
namespace App\Http\Controllers;
use \App\Http\Controllers\Controller;

class ArticleController extends Controller {
    public function list() {
        return \Response::json(array(
            'status' => 0
        ));
    }
    public function add() {
        return \Response::json(array(
            'status' => 0
        ));
    }
}

在路由配置(/routes/web.php)中添加:

Route::get('articles', 'ArticleController@list');
Route::post('articles', 'ArticleController@add');

php artisan sereve启动服务器后,访问首页,在Chrome中打开控制台。

使用fetch API输入

  1. fetch('/articles'),GET方法,得到正常的结果

  2. fetch('/articles', { method: 'POST' }),得到 TokenMismatchException 的错误,Google了一下,说是跨域的问题。但我明显没有跨域;反证:如果有跨域,之前的 GET 方法也不可能会正常访问。

现在我有两个问题:

  1. 由于对后端开发不是很熟悉,我想请问一下,GET方法默认不跨域处理,POST方法默认跨域处理的原因是什么?后端开发框架为什么要默认采用这样的配置? 感觉没有理由啊。为什么不跨域的情况下POST方法也当作跨域处理了?不是应该在跨域的情况下才算跨域吗?

  2. Laravel怎么测试POST接口?我根据这篇文章介绍的,给POST的header加上了X-CSRF-TOKEN,照常在不跨域的情况下出现POST方法跨域的现象。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

嗳卜坏 2022-09-11 13:23:45

Laravel 会为每个活跃用户自动生成一个 CSRF "token" 。该 token 用来核实应用接收到的请求是通过身份验证的用户出于本意发送的,无论何时,当你需要定义一个 HTML 表单,你都应该在里面包含一个隐藏的 CSRF token ,只有这样,CSRF 保护中间件才会验证请求。所以应该是这样说:你的post请求都要带上csrf的token,附上文档https://laravel-china.org/doc...


然后你提出的那两个问题,我这样回答:

  1. 跨域是因为你ajax发起请求域名或者端口和请求的api接口不同而导致浏览器不能识别的问题,解决跨域一般是使用jsonp的方式,但laravel有一个很好用的扩展包叫laravel-cros,你可以在github搜一下看看,所以跨域和get或post这两个请求方式没有关系,而且跨域的问题只会出现在浏览器上,而app上没有跨域问题

2.关于接口测试推荐一款Chrome的插件叫:postman,很好用

3.如果你想用laravel写接口的话,应该使用的路由是routes目录下的api.php,而不是web.php,新版本的laravel把这两个个路由分离出来更清晰了。所以还是建议多看一下文档哦

想念有你 2022-09-11 13:23:45

对于这个问题,我想说的是,这里 并不是为了解决跨域问题的,而且与跨域没有一毛钱的关系。TokenMismatchException 这个异常是在使用Laravel的 CSRF 中间件的时候,验证Token无效的情况下才会抛出的异常。


public function handle($request, Closure $next)
{
    if (
        $this->isReading($request) ||
        $this->runningUnitTests() ||
        $this->shouldPassThrough($request) ||
        $this->tokensMatch($request)
    ) {
        return $this->addCookieToResponse($request, $next($request));
    }

    throw new TokenMismatchException;
}

/**
 * Determine if the HTTP request uses a ‘read’ verb.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return bool
 */
protected function isReading($request)
{
    return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}

为什么会采用这样的配置这个问题也就变成了为什么要采用这个中间件,这个可以百度一下什么是CSRF(跨站请求伪造攻击),在Laravel中就是为了防御这种攻击方式的。

夜灵血窟げ 2022-09-11 13:23:45

跨域的处理的原理去了解了解,了解下为什么加token!

甜尕妞 2022-09-11 13:23:45

表单中添加{{csrf_field()}},好像是laravel自己设定的表单提交验证方式,具体也没研究过。

墨离汐 2022-09-11 13:23:45

今天刚好碰到这个问题。大概研究了一下。
这个问题重点不是跨域的问题,主要在于是否向服务器发送的请求头部中是否包含laravel的token令牌。
第一、jQuery的ajax验证
出现TokenMismatchException的原因是,laravel在接受来自客户端请求的时候要进行token验证。如果后台接受不到token字符串,则会返回 419 unknown status(这是laravel默认的防止跨站请求伪造做的安全验证,刚接触laravel的时候都会犯这个错误。就是发送了头部没有带token的ajax请求。具体的验证可以参考这篇文章http://laravelacademy.org/pos...)。laravel防跨站请求伪造原理:
1)首先Laravel开启Session时会生成一个token值并存放在Session中(IlluminateSessionStore.php第90行start方法),对应源码如下:

public function start()
{
    $this->loadSession();

    if (! $this->has('_token')) {
        $this->regenerateToken();
    }

    return $this->started = true;
}

2)然后重点分析VerifyToken中间件的handle方法,该方法中先通过isReading方法判断请求方式,如果请求方法是HEAD、GET、OPTIONS其中一种,则不做CSRF验证;

3)再通过shouldPassThrough方法判断请求路由是否在$excpet属性数组中进行了排除,如果做了排除也不做验证;

4)最后通过tokensMatch方法判断请求参数中的CSRF TOKEN值和Session中的Token值是否相等,如果相等则通过验证,否则抛出TokenMismatchException异常。

对应源码如下:

public function handle($request, Closure $next)
{
    if ($this->isReading($request) || $this->shouldPassThrough($request) || $this->tokensMatch($request)) {
        return $this->addCookieToResponse($request, $next($request));
    }

    throw new TokenMismatchException;
}

如果使用jQuery的ajax方法进行发送请求,需要在请求头部添加X-CSRF-TOKEN。代码如下:

$.ajax({
     headers: {
       'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    },
    url:"{{url('/registerProgress')}}",
    data:registerData,
    dataType:"JSON",
    type:"POST",
    success:function(response){
     }
});

第二、javascript的fetch请求。
你的问题重点在于使用fetch进行请求。出错原因重点在于:fetch的请求默认头部是不会发送本地cookie的。所以会出现TokenMismatchException的错误,因为根本接受不到客户端的令牌字符串。

credentials是Request接口的只读属性,用于表示用户代理是否应该在跨域请求的情况下从其他域发送cookies。这与XHR的withCredentials
标志相似,不同的是有三个可选值(后者是两个):

omit: 从不发送cookies.
same-origin: 只有当URL与响应脚本同源才发送 cookies、 HTTP Basicauthentication 等验证信息.
include: 不论是不是跨域的请求,总是发送请求资源域在本地的 cookies、 HTTPBasic authentication 等验证信息.

在Request对象中,request的credentials默认值是omit,也就是从不发送cookie。在fetch方法的参数中,fetch的第二个参数就是初始化的request参数值。request的credentials默认是不发送cookie的。可以参考该篇文章:https://developer.mozilla.org...。所以,你在使用fetch的时候如果没有设置credentials的值为same-origin则后台接不到请求头部的X-CSRF-TOKEN值。于是返回419状态码。
总之,原因就是请求头部没有本地的token导致的。
解决方式就是在fetch的第二个参数中,将credentials的值设置为same-origin或者include则可解决该问题。示例代码:

let myInit = {
     method: 'POST',
     headers: myHeaders,
     mode: 'cors',
     cache: 'default',
     credentials: "same-origin",
     body: new FormData(document.getElementById('form'))
};
fetch(url,myInit)...
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文