关于Laravel中TokenMismatchException报错的一点点总结

最近发现服务器上 Laravel日志中 TokenMismatchException的报错有点多 找了下原因和资料 来做个小小总结把

先来讲下 CSRF 攻击

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF
大概就是 攻击者盗用用户身份,发送恶意请求 来达到攻击者的一些目前 造成用户的隐私以及财产损失啦等等
CSRF攻击需要满足
1.用户登录受信任网站A,并在本地生成Cookie。
2.用户在不登出A的情况下,访问危险网站B。
具体的可以去下面看下 浅谈CSRF攻击方式 这篇文章写的很详细有兴趣的可以去看下


Laravel CSRF令牌

Laravel 自动为每一个被应用管理的有效用户会话生成一个 CSRF “令牌”,该令牌用于验证授权用户和发起请求者是否是同一个人。把这个令牌放入 form表单 的隐藏域中提交的时候一起提交
Laravel中提供了帮助函数csrf_field 来帮助我们添加 csrf 令牌

在模版文件使用

1
<?php echo csrf_field(); ?>

该辅助函数会帮我们生成对应HTML代码如下:

1
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">

也可以使用blade模版引擎的方式:

1
{!! csrf_field() !!}

不需要我们自己去编写后台的验证代码,Laravel使用了自带的中间件VerifyCsrfToken将请求中输入的 token 值和 Session 中的存储的 token 作对比来进行验证 更多Laravel这个中间件的详细用法请看 Laravel路由

VerifyCsrfToken 中间件

在Laravel 5.1及更高的版本中VerifyCsrfToken中间件已经被加入到了中间件组web中 在app/Http/Kernel.php中可以看到

1
2
3
4
5
6
7
8
9
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
//csrf验证中间件
\App\Http\Middleware\VerifyCsrfToken::class,
],

然后我们可以看到在RouteServiceProvider.php中默认加载了web中间件组 也就是我们所有的请求都会被这个中间件校验 如果你是做API接口的项目可以把这个中间件屏蔽掉 直接注释掉

TokenMismatchException 产生的原因

回到正题 关于 TokenMismatchException 产生的原因大概总结了以下

1.表单请求中没有带上校验token
2.被人攻击了 请求中伪造的token或者没有token导致的校验不通过
3.session过期 [这个比较坑找了好久]

config/session.php文件中可以看到默认的session时间是120分钟 其实这个已经足够了 如果不满意自己加吧 这个默认时间单位是分钟哦

回到VerifyCsrfToken.php中 我们可以看到它实际上是继承自Illuminate\Foundation\Http\Middleware\VerifyCsrfToken 这个父类,具体的生成 token 以及对 token 的校验都是在这里完成的 在它父类的大概第 55 行左右

1
2
3
4
5
6
7
8
9
10
11
12
public function handle($request, Closure $next)
{
if (
$this->isReading($request) ||
$this->runningUnitTests() ||
$this->shouldPassThrough($request) ||
$this->tokensMatch($request)
) {
return $this->addCookieToResponse($request, $next($request));
}
throw new TokenMismatchException;
}

TokenMismatchException就是在这里被抛出

解决办法

对于第一种错误 可以直接在表单提交的时候带上校验token就可以了 具体就是加上csrf_field就可以了

对于第二种 我的想法就是直接给他报错好了 (233)

对于第三种 网上找了好多也没啥好的解决办法 大概就是做了简单的跳转处理 我觉得这样也可以哦,如果你有空可以把交互做的好一点 于是就用这种办法先做了下

本来准备直接动刀 Illuminate\Foundation\Http\Middleware\VerifyCsrfToken这个类去做添加处理判断的 后来听了 mikelee 童鞋的提醒还是回到 app/Http/Middleware/VerifyCsrfToken.php来做更改(多谢提醒)【框架底层的东东尽量还是别动的好】

app/Http/Middleware/VerifyCsrfToken.php增加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function handle($request,Closure $next){
try{
parent::handle($request,$next);
}catch (\Exception $e){
if($e instanceof TokenMismatchException){
if($request->ajax()){
return response()->json(array('status'=>false,'msg'=>'您停留的时间过长,页面已过期,请刷新后重试'));
}else{
//针对一些post请求啦什么的
return redirect()->to('errorss')->send();
}
}
}
return $next($request);
}

注意:需要引入use Closure;use Illuminate\Session\TokenMismatchException;

我上面的那个errorss是路由中配置的 具体的就是对应一个方法指向了一个404页面 具体的方法内容随便你喽不用直接模仿我这个 你可以做的更炫酷 更友好的提示页面耶可以

1
2
3
4
5
6
return redirect()->to('errorss')->send();
return redirect()->action('HomeController@errorss')->send();
return redirect()
->back()
->withInput($request->except('_token'))
->withMessage('this is a 404 page')->send();

如果本地为了测试这个报错的话 需要更改本地配置文件中间的session时间,有可能会收不到redirect返回带回的信息哦 因为他返回带的信息都是存在session中的 如果你设置的时间过短会读不到这个 with 的信息

大体就这样吧 如果你有什么更好的办法可以 分享给我哦 3Q

-------------The End-------------