【发布时间】: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
标签: php laravel vue.js jwt single-page-application