如何使用Laravel Echo和Socket.io进行身份验证以加入私人频道

时间:2019-05-17 04:24:08

标签: reactjs laravel websocket socket.io laravel-echo

我正在使用Laravel 5.8.10,React 16.8,Laravel Echo Server 1.5.2,Redis 3.2.9和Socket.io 2.2.0。

我不使用Pusher,也不想使用Pusher。

我正在尝试为站点用户创建一个基本的聊天系统。他们通常使用带有电子邮件和密码的会话身份验证进行登录-一切正常。

有两种类型的用户:品牌和有影响力的人。每个人都有自己的自定义防护(网络品牌和网络影响者)。所有会话警卫均正常工作。

我正在使用React构建聊天页面。 我可以成功加入公共频道并在该公共频道上接收消息。但是,问题是当我尝试将频道设为私有时。

当我尝试加入私人频道时,Laravel Echo Server将验证请求发送到:http://localhost:8000/broadcasting/auth

但这会返回以下401错误:

{"message":"Unauthenticated."}
Client can not be authenticated, got HTTP status 401

现在,我正在尝试使用存储在用户表(品牌和影响者是2个用户表)中的简单“ api_token”对对/ broadcasting / auth的请求进行身份验证。这是一个唯一的60个字符的字符串。

我正在尝试这种“ api_token”策略,因为它听起来比设置Laravel Passport容易,但也许我错了。

这是我的React页面中的构造方法:

import React, { Component } from 'react';
import Echo from "laravel-echo";
import Socketio from "socket.io-client";

constructor(props) {
        super(props);
        this.state = {
            currentConversationId: conversations[0].id,
            data: '',
        };
        this.selectConversation = this.selectConversation.bind(this);

        let echo = new Echo({
            broadcaster: 'socket.io',
            host: 'http://localhost:6001',
            client: Socketio,
            auth: {
                headers: {
                    // I currently have CSRF requirements disabled for /broadcasting/auth,
                    // but this should work fine once it is enabled anyway
                    'X-CSRF-Token': document.head.querySelector('meta[name="csrf-token"]'),

                    // I have the api_token hard-coded as I am trying to get it to work,
                    // but I have also used the javascript variable 'token' below 
                    'api_token':'uUOyxRgCkVLKvp7ICZ0gXaELBPPbWEL0tUqz2Dv4TsFFc7JO4gv5kUi3WL3Q',
                    'Authorization':'Bearer: ' +'uUOyxRgCkVLKvp7ICZ0gXaELBPPbWEL0tUqz2Dv4TsFFc7JO4gv5kUi3WL3Q',

                    //'api_token':token,
                    //'Authorization':'Bearer: ' + token,
                }
            }
        });

        // Note that the ID of 1 is hardcoded for now until I get it to work
        echo.private('brand.1')
            .listen('SimpleMessageEvent', event => {
                console.log('got something...');
                console.log(event);
                this.state.data = event;
            });
    }

在这里您可以在$ php artisan route:list中看到该路由,使用的是auth:api中间件:

| GET|POST|HEAD | broadcasting/auth | Illuminate\Broadcasting\BroadcastController@authenticate | auth:api  

这是我的BroadcastServiceProvider.php:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;

class BroadcastServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Broadcast::routes(['middleware' => ['auth:api']]);

        require base_path('routes/channels.php');
    }
}

这是我的auth.php:

<?php

return [

    'defaults' => [
        'guard' => 'web-brands',
        'passwords' => 'brands',
    ],

    'guards' => [
        'web-brands' => [
            'driver' => 'session',
            'provider' => 'brands',
        ],

        'web-influencers' => [
            'driver' => 'session',
            'provider' => 'influencers',
        ],


        'api' => [
            'driver' => 'token',
            'provider' => 'brands2',
        ],
    ],

    'providers' => [
        'brands' => [
            'driver' => 'eloquent',
            'model' => App\Brand::class,
        ],

        'influencers' => [
            'driver' => 'eloquent',
            'model' => App\Influencer::class,
        ],
        'brands2' => [
            'driver' => 'database',
            'table' => 'brands',
        ],
    ],

    'passwords' => [
        'brands' => [
            'provider' => 'brands',
            'table' => 'password_resets',
            'expire' => 60,
        ],
        'influencers' => [
            'provider' => 'influencers',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

];

这是我的channels.php:

Broadcast::channel('brand.{id}',true);

请注意,我拥有这个品牌。{id}将其设置为默认返回true。我也为channels.php尝试过这个:

Broadcast::channel('brand.{id}', function ($brand,$id) {
    return $brand->id === Brand::find($id)->id;
});

我已经尝试通过使用虚拟路由来测试简单的api_token方法:

Route::get('test-test-test',function(){return 'asdf';})->middleware('auth:api');

此测试有效:

http://localhost:8000/test-test-test results in redirect
http://localhost:8000/test-test-test?api_token=123 results in redirect
http://localhost:8000/test-test-test?api_token=[the actual correct 60-character token] results in 'asdf'

以下是我的.env中的一些信息:

BROADCAST_DRIVER=redis
QUEUE_DRIVER=redis
CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

这是我的laravel-echo-server.json:

{
    "authHost": "http://localhost:8000",
    "authEndpoint": "/broadcasting/auth",
    "clients": [],
    "database": "redis",
    "databaseConfig": {
        "redis": {},
        "sqlite": {
            "databasePath": "/database/laravel-echo-server.sqlite"
        }
    },
    "devMode": true,
    "host": null,
    "port": "6001",
    "protocol": "http",
    "socketio": {},
    "sslCertPath": "",
    "sslKeyPath": "",
    "sslCertChainPath": "",
    "sslPassphrase": "",
    "subscribers": {
        "http": true,
        "redis": true
    },
    "apiOriginAllow": {
        "allowCors": false,
        "allowOrigin": "",
        "allowMethods": "",
        "allowHeaders": ""
    }
}

也许我没有在laravel echo请求的标头中正确发送api_token吗?

更新/编辑:

现在,我尝试删除/ broadcasting / auth路由的auth:api中间件。我不确定这是否正确。

现在会产生403错误:

Client can not be authenticated, got HTTP status 403

更新2-重要

所以我知道这是不推荐的,但是我开始在laravel源文件中进行一些更改...我终于使它工作了,现在我已经弄清楚了,我想覆盖那些我改变了而不是实际改变它们。我确实保存了原件,所以我可以轻松地还原。

一大挑战是,在更改源文件时,我无法使用where()方法,只能使用find()方法来查找用户。

需要更改的关键函数是retrieveUser()(位于Illuminate / Broadcasting / Broadcasters / Broadcaster.php内部。

问题在于它一直试图运行:

return $request->user();

...但是该user()函数从未起作用,这就是为什么它总是返回403禁止错误的原因。我认为这是因为实际的Laravel Echo请求是从React(在javascript前端)发送的,因此没有用户对象附加到该请求。换句话说,就像来宾一样。这就解释了为什么公共频道有效,而私人频道无效。

我从来没有想过如何通过React获得与请求一起发送的用户信息,但是我确实想出了一种解决方法。

基本上我要做的事情:

  1. 在我的控制器中,加密用户ID并将其作为变量传递给javascript。
  2. 将加密的ID变量作为标题的一部分通过Echo请求传递。
  3. 修改retrieveUser()函数以使用find(Crypt :: decrypt($ id))而不是-> user()(或奇怪的是不允许where())来查找用户。

据我所知,从安全角度看,这似乎是一个不错的策略,但也许读者可以指出这是否正确。

要侵入专用频道,您必须知道您要收听的频道的ID,然后将其作为加密的变量传递到请求的标头中。

也许潜在的黑客可能会说他/她想收听私人频道“ brand.1”,而他们所要做的就是加密数字1并将其通过标头。我想我不知道该怎么做才能知道这是否可能。

无论如何,我现在的目标是:

  1. 将其转换为替代设置,而不是显式更改Laravel源代码。
  2. 确定通过请求标头传递加密ID是否足够安全以进行生产。

似乎标头中的加密ID(每次运行请求时都会更改)比简单地传递“ api_token”(大多数用户将其存储在用户表中)的安全性高似乎可以。

0 个答案:

没有答案