环境
v2.15.1
描述:
我正在努力使我的web应用程序的登录流工作。这个web应用程序是使用Nuxt 3编写的,用于运行在http://localhost:3000上的前端(SPA),而Laravel作为运行在http://localhost上的后端。
由于Nuxt 3使用的是fetch API,而且axios目前对Nuxt 3不可用,所以我正在尝试使用Laravel和fetch API来实现登录流。
复制步骤:
我使用前端Nuxt应用程序上的可组合函数调用Laravel后端,如下所示:
export const useLogin = async (email, password) => {
const config = useRuntimeConfig()
const tokenResponse = await $fetch(config.baseURL + '/sanctum/csrf-cookie', {
method: 'GET',
credentials: 'include'
})
const token = getCookie('XSRF-TOKEN')
console.log(token)
const loginResponse = await $fetch(config.baseURL + '/login', {
method: 'POST',
headers: {
'X-XSRF-TOKEN': token,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: {
"username": email,
"password": password
}
})
}
然后在LoginForm组件中调用它:
<template>
<form @submit.prevent="login()" class="bg-blue-500 flex flex-col rounded-xl p-4 text-white">
<h1 class="text-center mb-4">Login</h1>
<label for="fname">Email</label>
<input v-model="form.email" class="rounded-md h-8 mb-4 text-black" type="text" id="email" name="email"><br><br>
<label for="password">Password</label>
<input v-model="form.password" class="rounded-md h-8 mb-4 text-black" type="password" id="password" name="password"><br><br>
<button class="rounded-md bg-orange-700 cursor-pointer" type="submit">Login</button>
</form>
</template>
<script setup>
const form = {
email: '',
password: ''
}
function login() {
useLogin(form.email, form.password)
}
</script>
问题是,我从登录路由获得了419个错误(csrf错配),尽管XSRF令牌是在请求头中设置的(如Laravel Sanctum文档中所描述的):
是否有可能由Laravel处理标题名称大小写敏感的问题,因此找不到令牌?据我所知,fetch API总是以小写格式发送头部名称,并且无法更改这种行为。
更多信息
cookie由Laravel后端正确设置:
cookie数据似乎也不错:
这些是我的.env
中的Sanctum值
SANCTUM_STATEFUL_DOMAINS='localhost,localhost:3000,127.0.0.1,127.0.0.1:3000,127.0.0.1:8000,::1'
SESSION_DOMAIN=.localhost
已试过
我还尝试在config/app.php
中设置正确的时区,我还更改了码头容器(laravel )内的时区,但是cookie仍然具有相同的数据值和过期时间,所以这似乎还可以。
如果我试图在一个全新安装的Laravel 9项目中注册一个新用户(该项目配置如下),我也会发生同样的行为:
请求/答复时间表:
* Preparing request to http://localhost/register
* Current time is 2022-06-09T10:42:49.472Z
* Using default HTTP version
* Disable timeout
* Enable automatic URL encoding
* Enable SSL validation
* Enable cookie sending with jar of 2 cookies
* Hostname in DNS cache was stale, zapped
* Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#21)
> POST /register HTTP/1.1
> Host: localhost
> User-Agent: insomnia/2022.3.0
> Cookie: laravel_session=eyJpdiI6IkpGdXZWRmh1WHdlc1F2VEpLVzllSFE9PSIsInZhbHVlIjoibk5JV0hmZHIzd1RpazRUOTJOOVBJZm1KNFZGUGlyaVJXTEk3NFl4YWVwSzRDNU5QQXh2Q1BrelNIWW55VWpEUTlXZmZaUjZPQUkrdjRDcnlCZUtkOENMMEhxOHVVWXhxcmFIdjdzRWh4dVgrLy9zRVhmOURFbFduR1hCcFIwcy8iLCJtYWMiOiJmZmZiZmI0MTk5OGNhZmRmMjFkZjgxMDk3MjgwMDBmYWFiNDA4YTZiYjQ1MjFkNjg0Mjk5MDRlMGEyNTU0NTUxIiwidGFnIjoiIn0%3D; XSRF-TOKEN=eyJpdiI6IkhtVjZrTWV2WC9KZFV0NWN4QUo2UVE9PSIsInZhbHVlIjoiQWJqQ2FuVHM1eENWZ212ZUZDNFZJVEErZk5ueGRUTnY3RFl3N2pJbEM4WEFTTE9UVFVWSXR1VkwvbkhhK1lqUkRySUJsWEtUT3RUWHI3UlcvTHpXSkNlNkNVZ3R2RXhkTVpWdWx2VmpZbDhPeWdTenVqVE5BWW1Rc1Qyb0t3L00iLCJtYWMiOiIwNTU2ZjgyMzdhNWU0Mjk1MzkyM2ZkN2Q4NWY2N2Y1MzAzYThlY2YxNmU2MzQyNTYxM2I3YjI2YjkyZjhiODZiIiwidGFnIjoiIn0%3D
> Content-Type: application/json
> Accept: application/json
> X-XSRF-TOKEN: eyJpdiI6IkhtVjZrTWV2WC9KZFV0NWN4QUo2UVE9PSIsInZhbHVlIjoiQWJqQ2FuVHM1eENWZ212ZUZDNFZJVEErZk5ueGRUTnY3RFl3N2pJbEM4WEFTTE9UVFVWSXR1VkwvbkhhK1lqUkRySUJsWEtUT3RUWHI3UlcvTHpXSkNlNkNVZ3R2RXhkTVpWdWx2VmpZbDhPeWdTenVqVE5BWW1Rc1Qyb0t3L00iLCJtYWMiOiIwNTU2ZjgyMzdhNWU0Mjk1MzkyM2ZkN2Q4NWY2N2Y1MzAzYThlY2YxNmU2MzQyNTYxM2I3YjI2YjkyZjhiODZiIiwidGFnIjoiIn0%3D
> Content-Length: 110
| {
| "name": "test",
| "email": "test7@local.de",
| "password": "test1234",
| "password_confirmation": "test1234"
| }
* Mark bundle as not supporting multiuse
< HTTP/1.1 419 unknown status
< Host: localhost
< Date: Thu, 09 Jun 2022 10:42:49 GMT
< Connection: close
< X-Powered-By: PHP/8.1.6
< Cache-Control: no-cache, private
< Date: Thu, 09 Jun 2022 10:42:49 GMT
< Content-Type: application/json
< Content-Length: 11025
* Replaced cookie laravel_session="eyJpdiI6IlhpSzV3SnJLOExyeXU0NGtaM0piZEE9PSIsInZhbHVlIjoiMkhsekNJYy80OTJHb3o2OWppZ2pSMDg2aHZuNTkzc1pYMXh2VDdHSW9XaUVPSlRkSkphZ013cnNEMW1CY1I1Zy9zNGpHTGtra0tEY1BQWGdhUlFUbWNzM3FJOVNqbTlNSEhxSXFSck1oWUlvbURvRFlYbW1oLytBbGJIUW1wYksiLCJtYWMiOiI1MGZjOGNhNTNmOTcxNTg1MjhkY2FmZjcwMWYwODBlZGE1NzYwZjU2MGJiNzRlZDk0NTU3YzBmZmUxZTVjYmNiIiwidGFnIjoiIn0%3D" for domain localhost, path /, expire 1654778569
< Set-Cookie: laravel_session=eyJpdiI6IlhpSzV3SnJLOExyeXU0NGtaM0piZEE9PSIsInZhbHVlIjoiMkhsekNJYy80OTJHb3o2OWppZ2pSMDg2aHZuNTkzc1pYMXh2VDdHSW9XaUVPSlRkSkphZ013cnNEMW1CY1I1Zy9zNGpHTGtra0tEY1BQWGdhUlFUbWNzM3FJOVNqbTlNSEhxSXFSck1oWUlvbURvRFlYbW1oLytBbGJIUW1wYksiLCJtYWMiOiI1MGZjOGNhNTNmOTcxNTg1MjhkY2FmZjcwMWYwODBlZGE1NzYwZjU2MGJiNzRlZDk0NTU3YzBmZmUxZTVjYmNiIiwidGFnIjoiIn0%3D; expires=Thu, 09 Jun 2022 12:42:49 GMT; Max-Age=7200; path=/; domain=.localhost; httponly; samesite=lax
* Received 10.8 KB chunk
* Closing connection 21
* Saved 1 cookie
错误信息:
{
"message": "CSRF token mismatch.",
"exception": "Symfony\\Component\\HttpKernel\\Exception\\HttpException",
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php",
"line": 379,
"trace": [
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php",
"line": 353,
"function": "prepareException",
"class": "Illuminate\\Foundation\\Exceptions\\Handler",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 51,
"function": "render",
"class": "Illuminate\\Foundation\\Exceptions\\Handler",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 185,
"function": "handleException",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php",
"line": 49,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\View\\Middleware\\ShareErrorsFromSession",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php",
"line": 121,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php",
"line": 64,
"function": "handleStatefulRequest",
"class": "Illuminate\\Session\\Middleware\\StartSession",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\Session\\Middleware\\StartSession",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php",
"line": 37,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php",
"line": 67,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\Cookie\\Middleware\\EncryptCookies",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 116,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 726,
"function": "then",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 703,
"function": "runRouteWithinStack",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 667,
"function": "runRoute",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 656,
"function": "dispatchToRoute",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"line": 167,
"function": "dispatch",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 141,
"function": "Illuminate\\Foundation\\Http\\{closure}",
"class": "Illuminate\\Foundation\\Http\\Kernel",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
"line": 21,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php",
"line": 31,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
"line": 21,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php",
"line": 40,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\TrimStrings",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php",
"line": 27,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php",
"line": 86,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php",
"line": 49,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\Http\\Middleware\\HandleCors",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php",
"line": 39,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 180,
"function": "handle",
"class": "Illuminate\\Http\\Middleware\\TrustProxies",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 116,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"line": 142,
"function": "then",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"line": 111,
"function": "sendRequestThroughRouter",
"class": "Illuminate\\Foundation\\Http\\Kernel",
"type": "->"
},
{
"file": "/var/www/html/public/index.php",
"line": 52,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Kernel",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/resources/server.php",
"line": 16,
"function": "require_once"
}
]
}
我不知道如何进一步调查。
发布于 2022-06-13 15:50:02
我错误地解释了我的错误日志,发现使用Axios和Fetch API还有一个小的区别: Axios似乎解码XSRF,而Fetch API没有。
因此,这个示例令牌由Fetch API:eyJpdiI6IkRScW9GNGtIbndWWFdKbzRiN0VWVkE9PSIsInZhbHVlIjoiNUNuQStiZGt0Y3l0cXhReThHSEJxbCsxRmZvbFFHKzhQV3ArSlg0cjVQVm5qSDZoQ3ZsSERnUTREUXRvczdMOHhYSHFqbm5FUjd2dFpGMlN4bU81NWJ5SWpmem5BQi9vRmJBRFZZWmFSZFlqcHNxbTZ5N1Z0cGJzSmMwcUFRaXUiLCJtYWMiOiI4MjM1MTQ2ODExNzhlY2ExNDk1NDhhOWEwNzE0OWJlMzViOGQxNDJhMTY0YTI2NzYwMThjMzQ5ODVmMDYwMjk1IiwidGFnIjoiIn0%3D
发送。
此示例标记由Axios:eyJpdiI6IisydDZOOFJoREp5ZnZudUtjRk1teXc9PSIsInZhbHVlIjoiV1dQdjJDZlNrcW43Zlg4TW1yRFdjOVJWVnJkaC9CZndxejJLQ3JEUkJIRXJ3Z2pNb2pxSUJYN0Y2RDBxZ1hKd01mNHF6empsRkFIeEFOSkJKbi8vT0hWOFBYMDdlMkZybzBZdllJQlBFa1lHTytZd1E0aU9vL2pOM1ZRZWwxV0ciLCJtYWMiOiI3NTczZGJiMjg5MWZmOTUzMjhiMTZhNjAxM2ZiZmVjODVjYjc2MGRiMGJkMTFkOGYzOWQzYWQ5MjI5YWIwOTA5IiwidGFnIjoiIn0=
发送。
在令牌字符串的末尾有一个很小的差别(= vs.%3d)。
Laravel本身发送令牌值URIEncoded (带有%3D),但似乎期望令牌在结尾处为=。
在文档中没有这方面的任何内容,但是这个问题让我找了好几天,而且并没有那么明显。
我把我的代码改为:
export const useLogin = async (email, password) => {
const config = useRuntimeConfig()
const tokenResponse = await $fetch(config.baseURL + '/sanctum/csrf-cookie', {
method: 'GET',
credentials: 'include'
})
const token = decodeURIComponent(getCookie('XSRF-TOKEN')) // <---- CHANGED
console.log(token)
const loginResponse = await $fetch(config.baseURL + '/login', {
method: 'POST',
credentials: 'include',
headers: {
'X-XSRF-TOKEN': token,
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: {
"email": email,
"password": password
}
})
}
现在,登录使用了标准的Fetch API。
我希望这有助于其他人调试他们的登录流。
欢迎光临。
发布于 2022-06-08 20:31:11
这是由于页面过期而发生的,因为它是post请求。您可以在.env文件中添加以下凭据并运行php artisan config:cache吗?
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost
那就看看结果。YOu还应该将标头标记作为X令牌传递。
https://stackoverflow.com/questions/72551784
复制相似问题