【问题标题】:Get CSRF Token missmatch - Vue axios against Laravel Api Route获取 CSRF 令牌不匹配 - Vue axios 针对 Laravel Api 路由
【发布时间】:2021-11-28 04:47:32
【问题描述】:

我正在尝试将客户购物车保存在服务器端的会话中。客户不需要登录。所以没有 Laravel Sanctum 或 Passport 等。Laravel 和 Vue 运行在不同的服务器上(后端:localhost:8000 和前端:localhost:9000)。

这是我的 api.php 路线。

Route::get('get-token', function (Request $request) {
    return [
        '_token' => $request->session()->token()
    ];    
})->middleware('web');

Route::group(['prefix'=>'v1'], function() {
    Route::resource('cart', App\Http\Controllers\Api\V1\CartController::class)->middleware('web');
    Route::resource('products', App\Http\Controllers\Api\V1\ProductController::class);
}

GET /cart 请求通过 axios 和 insomnia 都可以正常工作:

这里是 corospond CartController 功能索引:

function index() {
    return request()->session()->get('cart');
}

现在的问题

但是通过 axios 的 POST 请求被 419 CSRF token mismatch 拒绝。

奇怪的是,我可以通过 Insomnia 发出 POST 请求。首先从服务器请求令牌,然后在 POST 请求的正文中指定 _token。

> POST /api/v1/cart HTTP/1.1
> Host: localhost:8000
> User-Agent: insomnia/2021.5.3
> Cookie: laravel_session=ZzbpJsfvV22NjO3TB5C3ekiCIp7hlPAcaaZHTkfU; XSRF-TOKEN=eyJpdiI6IjhvVFpWMWtwMm0vU1RKNHB2VzdMdUE9PSIsInZhbHVlIjoiaDNEWTlhcTBRTWI0aXhkaU54Z3I5VndoWFg4Q1A1WmpJUDI1S2hFRGtiMUUwYUl0OEN6S0Q3RVFDZDJXSTNvdTllUXMzN0o1RU94blpqVTdpdTcxaTVvZjlwdFk2Z3djeldteHp0R3Q4UEVjMEovbGk2SHJnVXZmbzRBUzI1RHkiLCJtYWMiOiI2MDMyYzFjNWVjODY4NjRjMTk4NGNmMmI3Yjg4M2VjYzU5YzcwZGRkNDIxMTRiOTc1N2FkMmQ2NTc4YTA5MjhiIiwidGFnIjoiIn0%3D
> Content-Type: multipart/form-data; boundary=X-INSOMNIA-BOUNDARY
> Accept: application/json
> Content-Length: 293

| --X-INSOMNIA-BOUNDARY
| Content-Disposition: form-data; name="product_id"
| 1
| --X-INSOMNIA-BOUNDARY
| Content-Disposition: form-data; name="amount"
| 1
| --X-INSOMNIA-BOUNDARY
| Content-Disposition: form-data; name="_token"
| YgKRkQc9D5GWOPiP8zqVEcoA4FuvESf02SSjy7U3
| --X-INSOMNIA-BOUNDARY--

我绝对不明白为什么我在 api 路由中使用 web middleweare 的一个功能。 api 中间件会为我生成这个 500 错误消息:

"message": "Session store not set on request.",

这是我的 axios 请求:

        axios.get('http://localhost:8000/api/get-token').then(response => {
            const _token = response.data._token
            const bodyFormData = new FormData()
            bodyFormData.append('product_id', product.id)
            bodyFormData.append('_token', _token)
            const headers = {
              'Content-Type': 'multipart/form-data',
              'Access-Control-Allow-Credentials': true,
              '_token': _token             
            }

            axios({
              method: "post",
              url: 'cart',
              data: bodyFormData,
              headers: headers,
            }).then((res) => {
              console.log('res',res)
            }).catch((err) => {console.log(err)})

        }); 

这真的是一个很长的文本。我希望我已经提供了所有必要的信息。

【问题讨论】:

    标签: php laravel vue.js axios session-cookies


    【解决方案1】:
    1. 一个功能我绝对不明白为什么我在 api 路由中使用 web 中间件。
    • 这是因为API路由没有为应用启用会话,因为它期望应用使用Token,所以为了使用会话,你需要有“Web”中间件

    令牌不匹配错误是因为通过 get-token 端点生成/提供的令牌不是正确的

    • 我看到的解决方案是添加 /cart 路由作为 CSRFTokenMatch 的异常路由,打开 App/Http/Middleware/VerifyCsrfToken.php 文件并在受保护的 $except 数组中添加路由。这不是一件理想的事情,但这会暂时解决您的问题并帮助您继续前进。

    使您的应用程序安全的另一个选择是使用 Laravel Sanctum 并为每个访客用户生成一个令牌(使用访客模型),如果您需要帮助,我可以解释更多,但它的场景有点不同

    【讨论】:

    • Ravel 感谢第一个快速修复,我把 api/v1/cart 路由放在了 $exept 数组中。我可以将请求带到服务器上。我的问题是它认为所有其他请求都是新的。这意味着我没有独特的购物车,但有很多会话。当我重新加载页面时,购物车中没有任何内容。你有另一个快速修复;-)?
    • 是的,会话肯定有点棘手,但让我给你一些提示,虽然我不确定它是否会起作用。
    • 1.您使用 request()->session()->getId() 从您的请求中获取会话 ID,然后使用 localStorage.setItem('session_id', res.data.sessionId) 将此会话保存在您的 vue 应用程序中
    • 2.使用 bodyFormData.append('session_id', localStorage.getItem('session_id')) 在您的 axios 发布请求中发送会话 ID
    • 3.现在在 Laravel 端,尝试通过 request()->session()->setId(request('session_id')) 重新初始化会话
    【解决方案2】:

    我的第一个猜测是您没有发送 cookie,需要这样做

    axios.defaults.withCredentials = true
    

    或者,更安全(因为它将withCredentials 限制在您真正想要使用它的时间):

    const axiosWithCredentials = axios.create({
         withCredentials: true
    });
    axiosWithCredentials.get('http://localhost:8000/api/get-token').then(response => {
            const _token = response.data._token
            const bodyFormData = new FormData()
            bodyFormData.append('product_id', product.id)
            bodyFormData.append('_token', _token)
            const headers = {
              'Content-Type': 'multipart/form-data',
              'Access-Control-Allow-Credentials': true,
              '_token': _token             
            }
    
            axiosWithCredentials({
              method: "post",
              url: 'cart',
              data: bodyFormData,
              headers: headers,
            }).then((res) => {
              console.log('res',res)
            }).catch((err) => {console.log(err)})
    
        }); 
    

    【讨论】:

    • 谢谢戴夫。还是一样的响应 HTTP Status = 419
    • 好的,进一步看,尽量不要设置 Content-Type 标头,让 axios 解决问题 - 所有多部分类型共享通用语法,并包含边界参数作为媒体类型值的一部分,你没有。不过这里反正你也不需要,axios应该自己搞定的,只要你不手动设置就行了
    猜你喜欢
    • 2020-04-14
    • 2020-06-28
    • 2020-06-27
    • 2021-10-22
    • 2021-03-16
    • 2020-07-08
    • 2016-12-10
    • 2016-09-20
    • 2020-01-20
    相关资源
    最近更新 更多