使用 Laravel 作为后端刷新 Vue.js SPA 的身份验证令牌

     2023-02-22     260

关键词:

【中文标题】使用 Laravel 作为后端刷新 Vue.js SPA 的身份验证令牌【英文标题】:Refreshing authentication tokens for a Vue.js SPA using Laravel for the backend 【发布时间】:2018-04-12 11:20:50 【问题描述】:

我正在使用 Laravel (5.5) 作为后端构建一个带有 Vue (2.5) 的单页应用程序。一切正常,除了注销后直接再次登录。在这种情况下,对 /api/user 的调用(以检索用户的帐户信息并再次验证用户的身份)失败并出现 401 未授权(即使登录成功)。作为响应,用户被直接弹回登录屏幕(我自己编写了这个度量作为对 401 响应的响应)。

有效的方法是注销,使用 ctrl/cmd+R 刷新页面,然后再次登录。页面刷新解决了我的问题,让我有理由相信我没有正确处理 X-CSRF-TOKEN 的刷新,或者可能忘记了 Laravel 使用的某些 cookie(如 here 所述)。

这是用户单击登录按钮后执行的登录表单代码的 sn-p。

login()
    // Copy the form data
    const data = ...this.user;
    // If remember is false, don't send the parameter to the server
    if(data.remember === false)
        delete data.remember;
    

    this.authenticating = true;

    this.authenticate(data)
        .then( this.refreshTokens )
        .catch( error => 
            this.authenticating = false;
            if(error.response && [422, 423].includes(error.response.status) )
                this.validationErrors = error.response.data.errors;
                this.showErrorMessage(error.response.data.message);
            else
                this.showErrorMessage(error.message);  
            
        );
,
refreshTokens()
    return new Promise((resolve, reject) => 
        axios.get('/refreshtokens')
            .then( response => 
                window.Laravel.csrfToken = response.data.csrfToken;
                window.axios.defaults.headers.common['X-CSRF-TOKEN'] = response.data.csrfToken;
                this.authenticating = false;
                this.$router.replace(this.$route.query.redirect || '/');
                return resolve(response);
            )
            .catch( error => 
                this.showErrorMessage(error.message);
                reject(error);
            );
    );
,  

authenticate() 方法是一个 vuex 动作,在 laravel 端调用登录端点。

/refreshTokens 端点只是调用这个 Laravel 控制器函数,它返回当前登录用户的 CSRF 令牌:

public function getCsrfToken()
    return ['csrfToken' => csrf_token()];

重新获取令牌后,用户将被重定向到主页(或其他页面,如果提供的话) 使用this.$router.replace(this.$route.query.redirect || '/'); 并在那里调用api/user 函数来检查当前登录用户的数据。

我是否应该采取任何其他措施来完成这项工作,但我忽略了?

感谢您的帮助!


编辑:2017 年 11 月 7 日

在所有有用的建议之后,我想补充一些信息。我在 Laravel 端使用 Passport 进行身份验证,并且 CreateFreshApiToken 中间件就位。

我一直在查看我的应用程序设置的 cookie,特别是 laravel_token,据说它保存了 Passport 将用来验证来自 JavaScript 应用程序的 API 请求的加密 JWT。注销时,laravel_token cookie 被删除。之后直接再次登录时(使用 axios 发送 AJAX 发布请求)没有设置新的laravel_token,所以这就是它不对用户进行身份验证的原因。我知道 Laravel 没有在登录 POST 请求上设置 cookie,但是之后直接对 /refreshTokens (不受保护)的 GET 请求应该设置 cookie。但是,这似乎没有发生。

我已尝试增加对/refreshTokens 的请求和对/api/user 的请求之间的延迟,以可能给服务器一些时间来整理东西,但无济于事。

为了完整起见,这里是我的 Auth\LoginController 正在处理登录请求服务器端:

class LoginController extends Controller

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    
        // $this->middleware('guest')->except('logout');
    

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(\Illuminate\Http\Request $request)
    
        //return $request->only($this->username(), 'password');
        return ['email' => $request->$this->username(), 'password' => $request->password, 'active' => 1];
    

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(\Illuminate\Http\Request $request, $user)
    
        $user->last_login = \Carbon\Carbon::now();
        $user->timestamps = false;
        $user->save();
        $user->timestamps = true;

        return (new UserResource($user))->additional(
            ['permissions' => $user->getUIPermissions()]
        );
    


    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function logout(\Illuminate\Http\Request $request)
    
        $this->guard()->logout();
        $request->session()->invalidate();
    

【问题讨论】:

“注销”功能是如何实现的? 它通过post请求调用laravel的注销功能。所以很简单axios.post('/logout'); 当您观察 Ajax 的注销/登录请求来回切换时,您是否看到 /refreshtokens 生成并返回了一个新令牌,并且该令牌用于后续(和失败)调用到服务器? 是的,我看到正在生成一个新令牌。我做了更多研究,发现了一些新信息:问题是 refreshToken 返回的令牌是用于防止跨站点请求伪造的 CSRF 令牌。它不是用于对用户进行身份验证的 JWT 令牌。这是在一个名为 laravel_token 的 cookie 中设置的,它是一个 httpOnly cookie,因此在 JS 中无法访问。许多消息来源说浏览器应该处理这个令牌,即使它是通过 ajax 响应返回的,但显然这里不是这种情况...... 这里有更多关于这个问题的信息:github.com/laravel/passport/issues/293 【参考方案1】:

考虑到您使用 api 进行身份验证,我建议使用 Passport 或 JWT Authentication 来处理身份验证令牌。

【讨论】:

谢谢。我正在使用护照。它说它应该根据laravel.com/docs/5.5/… 自动处理 JWT 身份验证。这很好用,除了注销登录问题。我的想法是我没有正确处理根据laravel.com/docs/5.5/csrf#csrf-x-csrf-token 随每个响应返回的 cookie 数据 啊抱歉不明显。我会检查您的配置和设置,并确保一切正确 我已经这样做了,但找不到任何异常。我认为我的程序是正确的,但我错过了难题的某些部分(比如处理 cookie 数据)。我怀疑我是第一个遇到这个问题的人,所以希望有人发现错误。 显然我将用于防止跨站点请求伪造的 CSRF 令牌与用于身份验证的 laravel_token cookie 混淆了。此 cookie 是 httpOnly 的,因此 JS 无法访问。它应该由浏览器自动处理,但在我的情况下不会发生这种情况。 (即使我通过调用 /refreshTokens 执行了设置 cookie 所需的额外 GET 请求)。 您的配置中显然缺少某些内容或不正确。来自开发工具的一些请求/响应转储会很有帮助。此外,请确保您已将 \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class 添加到您的 web 中间件组中,如此处所述 laravel.com/docs/5.5/…【参考方案2】:

终于修好了!

通过直接在 LoginControllers authenticated 方法中返回 UserResource,它不是有效的 Laravel 响应(但我猜是原始 JSON 数据?)所以可能没有附加 cookie 之类的东西。我必须在资源上附加对 response() 的调用,现在一切似乎都运行良好(尽管我需要进行更广泛的测试)。

所以:

protected function authenticated(\Illuminate\Http\Request $request, $user)

    ...

    return (new UserResource($user))->additional(
        ['permissions' => $user->getUIPermissions()]
    );

变成

protected function authenticated(\Illuminate\Http\Request $request, $user)

    ...

    return (new UserResource($user))->additional(
        ['permissions' => $user->getUIPermissions()]
    )->response();  // Add response to Resource

Laravel 文档中关于将一个部分归因于此的万岁: https://laravel.com/docs/5.5/eloquent-resources#resource-responses

此外,登录的 POST 请求没有设置 laravel_token,并且调用 refreshCsrfToken() 也没有成功,可能是因为它受到了来宾中间件的保护。

最终对我有用的是在登录功能返回(或承诺已履行)后立即对“/”执行虚拟调用。

最后我在组件中的登录功能如下:

login()
    // Copy the user object
    const data = ...this.user;
    // If remember is false, don't send the parameter to the server
    if(data.remember === false)
        delete data.remember;
    

    this.authenticating = true;

    this.authenticate(data)
        .then( csrf_token => 
            window.Laravel.csrfToken = csrf_token;
            window.axios.defaults.headers.common['X-CSRF-TOKEN'] = csrf_token;

            // Perform a dummy GET request to the site root to obtain the larevel_token cookie
            // which is used for authentication. Strangely enough this cookie is not set with the
            // POST request to the login function.
            axios.get('/')
                .then( () => 
                    this.authenticating = false;
                    this.$router.replace(this.$route.query.redirect || '/');
                )
                .catch(e => this.showErrorMessage(e.message));
        )
        .catch( error => 
            this.authenticating = false;
            if(error.response && [422, 423].includes(error.response.status) )
                this.validationErrors = error.response.data.errors;
                this.showErrorMessage(error.response.data.message);
            else
                this.showErrorMessage(error.message);  
            
        );

而我的 vuex 商店中的 authenticate() 动作如下:

authenticate( dispatch , data)
    return new Promise( (resolve, reject) => 
        axios.post(LOGIN, data)
            .then( response => 
                const csrf_token, ...user = response.data;
                // Set Vuex state
                dispatch('setUser', user );
                // Store the user data in local storage
                Vue.ls.set('user', user );
                return resolve(csrf_token);
            )
            .catch( error => reject(error) );
    );
,

因为除了对/ 的虚拟调用之外,我不想对refreshTokens 进行额外调用,因此我将 csrf_token 附加到后端的 /login 路由的响应中:

protected function authenticated(\Illuminate\Http\Request $request, $user)

    $user->last_login = \Carbon\Carbon::now();
    $user->timestamps = false;
    $user->save();
    $user->timestamps = true;

    return (new UserResource($user))->additional([
        'permissions' => $user->getUIPermissions(),
        'csrf_token' => csrf_token()
    ])->response();

【讨论】:

【参考方案3】:

您应该在 Web 中间件中使用 Passports CreateFreshApiToken 中间件passport consuming-your-api

web => [...,
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

此附件将正确的 csrftoken() 作为 request_cookies 附加到您的所有请求标头

【讨论】:

感谢您的建议,但建议已经到位,我正在使用 CreateFreshApiToken 中间件

将 Vue js 前端和 laravel 后端(api 路由)托管到共享服务器?

】将Vuejs前端和laravel后端(api路由)托管到共享服务器?【英文标题】:HostingaVuejsfrontendandlaravelbackend(apiroutes)toasharedserver?【发布时间】:2019-12-0920:09:42【问题描述】:我用vuejs作为前端库,laravel作为后端框架构建了一个单页web... 查看详情

vue.js的“Firebase + Validation Example”中如何使用mysql作为数据持久化后端?

...【发布时间】:2016-04-2800:09:15【问题描述】:我正在学习Laravel5.2和vue.js。问题是:vue.js的《F 查看详情

使用 Laravel 和 Vue.js 分离前端和后端

】使用Laravel和Vue.js分离前端和后端【英文标题】:SeparationoffrontendandbackendwithLaravelandVue.js【发布时间】:2017-06-1618:54:54【问题描述】:我们正计划为工作申请开发一个网络应用程序。在头两年,我们预计每天的访问量约为3,000-6,0... 查看详情

我想从 laravel 中的 vue.js 代码运行工匠命令

】我想从laravel中的vue.js代码运行工匠命令【英文标题】:Iwanttorunartisancommandfromvue.jscodeinlaravel【发布时间】:2020-01-2102:53:15【问题描述】:我使用vue.js作为前端,使用laravel作为后端。单击按钮时,我想从代码中运行工匠命令。... 查看详情

如何使用 vue.js laravel 自动显示数据而不刷新

】如何使用vue.jslaravel自动显示数据而不刷新【英文标题】:Howtoshowdataautomaticallywithoutrefreshwithvue.jslaravel【发布时间】:2020-01-1422:13:52【问题描述】:我无法立即看到已成功输入数据库的数据。我在Laravel5.8框架中使用vue.js,带有a... 查看详情

如何在 Vue.js 中显示错误数组?使用 Laravel 进行后端验证

】如何在Vue.js中显示错误数组?使用Laravel进行后端验证【英文标题】:HowtoshowArrayoferrorsinVue.js?BackendValidationwithLaravel【发布时间】:2021-06-2303:45:18【问题描述】:我有一些复杂的数据,我想在vue文件中显示验证错误数组数据,但... 查看详情

vue.js 组件使用 laravel 刀片作为模板

】vue.js组件使用laravel刀片作为模板【英文标题】:vue.jscomponentusinglaravelbladeforthetemplate【发布时间】:2016-03-1809:31:43【问题描述】:我的想法是在vue/vueify组件的<template></template>中使用laravel刀片。我不确定如... 查看详情

Laravel 响应良好实践

】Laravel响应良好实践【英文标题】:Laravelresponsesgoodpractice【发布时间】:2019-01-1820:44:30【问题描述】:我正在使用Laravel5.6作为我的个人项目的后端,并且我一直在做一些(在我看来)是不好的做法,无论哪种方式,我都想知道... 查看详情

如何在 Laravel TailwindCss Vue Js 项目中添加和使用本地自定义字体?

】如何在LaravelTailwindCssVueJs项目中添加和使用本地自定义字体?【英文标题】:HowtoaddanduseLocalcustomfontinaLaravel+TailwindCss+Vuejsproject?【发布时间】:2021-06-0905:49:01【问题描述】:正如标题所示,我正在尝试在我的项目中添加和使用... 查看详情

LARAVEL + Vue.js:使用刀片在 MPA 中显示整个页面的 vue.js 组件,对 SEO 不友好

】LARAVEL+Vue.js:使用刀片在MPA中显示整个页面的vue.js组件,对SEO不友好【英文标题】:LARAVEL+Vue.js:usingbladetoshowwholepagevue.jscomponentsinMPA,notSEOfriendly【发布时间】:2019-02-1911:23:42【问题描述】:所以我正在考虑我的餐厅网页的结构,... 查看详情

Vue Js 作为单独安装或将 VueJs 与 Laravel 一起使用

】VueJs作为单独安装或将VueJs与Laravel一起使用【英文标题】:VueJsasseparateInstallationoruseVueJswithinLaravel【发布时间】:2020-10-2916:59:27【问题描述】:我们将使用Laravel和VueJs开始一个项目。我们需要的是我们是否将VueJs作为单独的安装... 查看详情

有没有更好的方法来处理 laravel 和 vue js 的 axios 错误

】有没有更好的方法来处理laravel和vuejs的axios错误【英文标题】:isthereanybetterwaytohandlingaxioserrorwithlaravelandvuejs【发布时间】:2018-08-2201:46:48【问题描述】:所以我正在使用laravel作为我的后端和vuejs作为spa前端框架来创建spa网络应... 查看详情

无法通过 Vue.js(Axios 帖子)从 Laravel 后端下载文件(pdf)

】无法通过Vue.js(Axios帖子)从Laravel后端下载文件(pdf)【英文标题】:Unabletodownloadafile(pdf)fromLaravelbackendviaVue.js(Axiospost)【发布时间】:2019-03-0416:28:50【问题描述】:我在Vue中有一个多步骤表单,一旦我收集了所有信息,我就... 查看详情

如何在 google firebase 上托管我的 laravel 项目

】如何在googlefirebase上托管我的laravel项目【英文标题】:HowdoIhostmylaravelProjectongooglefirebase【发布时间】:2020-01-1013:29:18【问题描述】:我正在做一个项目,我使用Vue.js作为前端,使用laravel作为后端,使用mysql作为数据库。现在我... 查看详情

需要在 vue.js 前端和 Laravel 后端打印名称而不是 id

】需要在vue.js前端和Laravel后端打印名称而不是id【英文标题】:Needtoprintnameinsteadofidinvue.jsfrontendandLaravelbackend【发布时间】:2021-08-0213:38:11【问题描述】:这是我的CategoryController.php代码...publicfunctionname($id)$f=Category::findOrFail($id);$n... 查看详情

Laravel 使用 Socket.io 广播到私人频道

】Laravel使用Socket.io广播到私人频道【英文标题】:LaravelBroadcasttoaprivatechannelusingSocket.io【发布时间】:2018-03-0320:06:03【问题描述】:我正在尝试使用Laravel作为后端API客户端和SPA作为前端创建一个实时通知系统,我使用React作为前... 查看详情

Laravel + Vue JS:从数据库中获取/存储 Eloquent 模型的值

】Laravel+VueJS:从数据库中获取/存储Eloquent模型的值【英文标题】:Laravel+VueJS:Get/storevalueofEloquentModelfromDB【发布时间】:2017-08-2014:32:58【问题描述】:我使用vue.js2为前端和Laravel作为后端API构建了一个SPA。现在我尝试在用户登录... 查看详情

如何使用 Laravel 作为 Angular SPA 应用程序的后端?

】如何使用Laravel作为AngularSPA应用程序的后端?【英文标题】:HowtouseLaravelasthebackendforanAngularSPAapp?【发布时间】:2020-07-1421:20:18【问题描述】:我想用Angular(前端)和Laravel(后端)创建一个网站。连接这两者的最佳方式是什么... 查看详情