如何将Laravel CSRF令牌传入Service Worker以发起POST请求
Laravel 结合 Service Worker 发起 POST 请求时的CSRF令牌问题
问题描述
我在Laravel应用中使用Service Worker实现缓存与Web推送通知功能,需要在推送通知弹出时向用户提供「接受」「拒绝」两个操作选项,用户点击选项后向Laravel发起POST请求更新用户状态。
当前service-worker.js代码如下:
self.addEventListener('notificationclick', function(event) { console.log('[Service Worker] Notification click Received.'); var notification = event.notification; var action = event.action; notification.close(); switch (action) { case 'close': console.log('[Service Worker] close Action Clicked'); break; case 'view': console.log('[Service Worker] view Action Clicked') event.waitUntil(clients.openWindow(notification.data.url)); break; case 'accept-player': console.log('[Service Worker] accept-player Action Clicked') fetch(notification.data.url, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: { accepted:1 }, }).catch(error => { console.error(error) }); break; case 'reject-player': console.log('[Service Worker] reject-player Action Clicked') fetch(notification.data.url, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: { accepted:0 }, }).catch(error => { console.error(error) }); break; } });
代码中针对accept-player、reject-player两个场景编写了fetch请求逻辑,但Service Worker运行环境不存在document对象,无法通过读取HTML meta标签的常规方式获取CSRF令牌,控制台抛出如下错误:
sw.js:86 Uncaught ReferenceError: document is not defined
需要可行方案解决该问题,暂不确定是否应当为对应路由关闭CSRF校验。
可行解决方案
方案1:推送下发时携带CSRF令牌(推荐,无需修改CSRF校验规则)
Service Worker独立于页面主线程运行,确实无法访问DOM对象,但完全不需要在SW运行时临时获取令牌——在服务端生成推送通知载荷时,直接把当前用户的有效CSRF令牌写入通知数据即可:
- 修改Laravel端推送发送逻辑,在通知的
data字段中附加CSRF令牌:
// Web推送下发逻辑示例 $webPush->queueNotification( $subscribedUser->push_subscription, json_encode([ 'title' => '入队申请', 'body' => '有玩家申请加入你的队伍', 'data' => [ 'url' => route('player.audit', $apply->id), 'csrf_token' => csrf_token(), // 直接生成当前用户的有效CSRF令牌 'actions' => [ ['action' => 'accept-player', 'title' => '接受'], ['action' => 'reject-player', 'title' => '拒绝'] ] ] ]) );
- 修改Service Worker中的fetch逻辑,直接从通知数据中读取令牌,同时修正两个原有代码问题:
- 所有异步操作必须包裹在
event.waitUntil()中,避免Service Worker在请求完成前被浏览器提前终止 - fetch传递JSON格式body时,必须用
JSON.stringify()序列化,不能直接传原生对象
- 所有异步操作必须包裹在
// accept-player分支修改后 case 'accept-player': console.log('[Service Worker] accept-player Action Clicked') event.waitUntil( fetch(notification.data.url, { method: 'POST', headers: { 'X-CSRF-TOKEN': notification.data.csrf_token, 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ accepted: 1 }), }).catch(error => { console.error(error) }) ); break; // reject-player分支做相同修改,body中accepted值传0即可 case 'reject-player': console.log('[Service Worker] reject-player Action Clicked') event.waitUntil( fetch(notification.data.url, { method: 'POST', headers: { 'X-CSRF-TOKEN': notification.data.csrf_token, 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ accepted: 0 }), }).catch(error => { console.error(error) }) ); break;
方案2:指定路由排除CSRF校验(适合简单场景)
如果不想修改推送下发逻辑,可以仅将处理通知点击回调的路由加入CSRF白名单,禁止全局关闭CSRF校验:
- 打开
app/Http/Middleware/VerifyCsrfToken.php,将对应路由加入$except数组:
protected $except = [ // 替换为你自己的回调路由路径 '/player/audit-callback', ];
- 同样按照方案1中的要求修改SW的fetch逻辑:用
event.waitUntil()包裹请求、序列化body即可,不需要再携带X-CSRF-TOKEN请求头。
注意:使用该方案时必须给回调接口加上严格的用户身份鉴权,比如通过请求参数关联的申请ID匹配对应用户权限,避免接口被恶意调用。
内容的提问来源于stack exchange,提问作者Mous
相关产品推荐
相关产品推荐

