使用Laravel为后端

时间:2017-10-31 10:17:38

标签: php laravel vue.js jwt single-page-application

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

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

这是在用户点击登录按钮后执行的登录表单代码的片段。

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日

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

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

我已经尝试增加/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();
    }
}

3 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:2)

最后解决了!

通过在LoginControllers authenticated方法中直接返回UserResource,它不是一个有效的Laravel响应(但我猜原始的JSON数据?)所以可能没有附加像cookies这样的东西。我不得不在资源上附加对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
}

Hurray为Laravel文档介绍了一个部分: https://laravel.com/docs/5.5/eloquent-resources#resource-responses

此外,laravel_token不是由POST请求设置登录的,而对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();
}

答案 2 :(得分:1)

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

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

此附件将权限csrftoken()附加到您的所有请求标头为request_cookies