Laravel Echo - 允许访客连接到出席频道

Posted

技术标签:

【中文标题】Laravel Echo - 允许访客连接到出席频道【英文标题】:Laravel Echo - Allow guests to connect to presence channel 【发布时间】:2017-09-06 14:21:30 【问题描述】:

我正在使用 laravel-echo-server 运行 Laravel Echo 来广播事件。

我有一个用户计数器频道,它显示应用程序上的所有用户。为此,我使用了一个存在渠道。这对于已登录的用户来说效果很好,但客人永远无法连接。

我在 BroadcastServiceProvider 中设置了以下内容:

Broadcast::channel('global', function () return ['name' => 'guest']; );

据我所知,应该允许每个人作为“客人”进入。我猜在此之前我需要为这个频道禁用一些中间件或身份验证。

非常感谢任何有关让所有客户加入此展示频道的帮助!

【问题讨论】:

【参考方案1】:

对于任何寻找此问题的答案的人。确实可以将客人授权到出席频道,您只需要用您自己的服务提供商覆盖 Broadcast::routes()。

例如,我的出席频道“全球”接受客人:

Route::post('/broadcasting/auth', function(Illuminate\Http\Request $req) if($req->channel_name == 'presence-global')return 'global'; return abort(403); );

这可以向各个方向扩展,或者可以继续将其他存在和私人频道传递给默认的 Broadcast::auth 方法

【讨论】:

然后,你如何加入房间?【参考方案2】:

您可以使用factory(User::class)->make(...) 创建一个临时用户,并使用中间件对其进行身份验证以将其用作访客。

第 1 步:创建中间件

运行:php artisan make:middleware AuthenticateGuest

app/Http/Middleware/AuthenticateGuest.php:

public function handle($request, Closure $next)

    Auth::login(factory(User::class)->make([
        'id' => (int) str_replace('.', '', microtime(true))
    ]));

    return $next($request);

现在在Kernel.php 中设置 AuthenticateGuest 中间件。

app\Http\Kernel.php:

protected $routeMiddleware = [
    ...
    'authenticate-guest' => \App\Http\Middleware\AuthenticateGuest::class,
];

第二步:设置广播::频道路由

routes/channels.php:

Broadcast::channel('chatroom', function ($user) 
    return $user; // here will return the guest user object
);

更多信息:https://laravel.com/docs/8.x/broadcasting#authorizing-presence-channels

【讨论】:

那么就跳过第 1 步,然后在第 2 步调整代码? 是的。我会更新我的答案以保持它更干净。感谢您的评论! 很好,只是想澄清一下,以便我可以尝试一下,并不是要吹毛求疵。 但是你连中间件都不用?它刚刚创建。您还需要在 BroadcastServiceProvider 的引导方法中注册它。否则它不会被调用。【参考方案3】:

其他解决方案不适用于访客通道,这就是我最终得到的结果:

// routes/channels.php

<?php
use Illuminate\Auth\GenericUser;

/*
|--------------------------------------------------------------------------
| Broadcast Channels
|--------------------------------------------------------------------------
|
| Here you may register all of the event broadcasting channels that your
| application supports. The given channel authorization callbacks are
| used to check if an authenticated user can listen to the channel.
|
*/

Route::post('/custom/broadcast/auth/route', function () 
    $user = new GenericUser(['id' => microtime()]);

    request()->setUserResolver(function () use ($user) 
        return $user;
    );

    return Broadcast::auth(request());
);

Broadcast::channel('online.uuid', function ($user, $uuid) 
    return [
        'id' => $user->id,
        'uuid' => $uuid
    ];
);



【讨论】:

【参考方案4】:

您可以创建自己的身份验证守卫,它也很简单,但更复杂。

    创建一个将实现可验证接口的类。 创建用户提供者。 创建一个新的 Guard。 在 AuthServiceProvider 中注册 Guard 和 UserProvider。 在config/auth.php中添加provider和guard 使用你的新警卫。

优势

您不必修改身份验证端点 您不必更改默认保护 你基于 Laravel Auth 系统 继续支持浏览器中的多个选项卡 可与网络防护同时使用 保留使用 PresenceChannel 的所有优势

缺点

要编写很多代码

所以,

1。创建一个将实现 Authenticable 接口的新类。

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use JsonSerializable;

/**
 * @property string $id
 * @property string $name
 */
class Session implements Authenticatable, Jsonable, Arrayable, JsonSerializable


    private $id;

    private $attributes = [];

    public function __construct($id)
    
        $this->id = $id;
        $this->name = "Guest";
    

    /**
     * Get the name of the unique identifier for the user.
     *
     * @return string
     */
    public function getAuthIdentifierName()
    
        return 'id';
    

    /**
     * Get the unique identifier for the user.
     *
     * @return mixed
     */
    public function getAuthIdentifier()
    
        return $this->$this->getAuthIdentifierName();
    

    /**
     * Get the password for the user.
     *
     * @return string
     */
    public function getAuthPassword()
    
        return "";
    

    /**
     * Get the token value for the "remember me" session.
     *
     * @return string
     */
    public function getRememberToken()
    
        return $this->$this->getAuthIdentifierName();
    

    /**
     * Set the token value for the "remember me" session.
     *
     * @param  string $value
     * @return void
     */
    public function setRememberToken($value)
    
        $this->$this->getRememberToken() = $value;
    

    /**
     * Get the column name for the "remember me" token.
     *
     * @return string
     */
    public function getRememberTokenName()
    
        return "token";
    

    public function __get($name)
    
        return $this->attributes[$name];
    

    public function __set($name, $value)
    
        $this->attributes[$name] = $value;
    

    /**
     * Convert the object to its JSON representation.
     *
     * @param  int $options
     * @return string
     */
    public function toJson($options = 0)
    
        return json_encode($this);
    

    /**
     * Get the instance as an array.
     *
     * @return array
     */
    public function toArray()
    
        return $this->attributes;
    

    /**
     * Specify data which should be serialized to JSON
     * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
     * @return mixed data which can be serialized by <b>json_encode</b>,
     * which is a value of any type other than a resource.
     * @since 5.4.0
     */
    public function jsonSerialize()
    
        return $this->attributes;
    


随意修改,但不要序列化 ​​$id 属性

2。创建用户提供者

<?php namespace App\Extensions;

use App\Models\Session;
use Illuminate\Cache\Repository;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Fluent;
use Illuminate\Support\Str;

class SessionUserProvider implements UserProvider


    private $store;

    /**
     * SessionUserProvider constructor.
     * @param Repository $store
     */
    public function __construct(Repository $store)
    
        $this->store = $store;
    


    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    
        return new Session(
            $this->getUniqueTokenForSession($identifier)
        );
    

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed $identifier
     * @param  string $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token)
    
        return null;
    

    /**
     * Update the "remember me" token for the given user in storage.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable $user
     * @param  string $token
     * @return void
     */
    public function updateRememberToken(Authenticatable $user, $token)
    
        return;
    

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials)
    
        return null;
    

    private function unpack($data)
    
        return json_decode($data);
    

    private function getUniqueTokenForSession($id)
    
        return $this->retrieveCacheDataForSession($id)
            ->get('uuid');
    

    private function retrieveCacheDataForSession($id)
    
        $fluent = new Fluent(
            $this->unpack(
                $this->store->has($id) ? $this->store->get($id) : "[]"
            )
        );

        if(!$fluent->__isset('uuid')) 
            $fluent->__set('uuid', Str::random(128));
        

        $this->store->put($id, $fluent->toJson(), 60 * 60 * 60);

        return $fluent;

    

    /**
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable $user
     * @param  array $credentials
     * @return bool
     */
    public function validateCredentials(Authenticatable $user, array $credentials)
    
        return null;
    

如果您使用广播,则 retrieveById 方法中的

Identifier 属性始终是会话 ID,因此您也可以将其用作令牌。

3。创建新的警卫

<?php namespace App\Services\Auth;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;

class GuestGuard implements Guard


    private $user;
    protected $request;
    protected $provider;

    /**
     * GuestGuard constructor.
     * @param UserProvider $provider
     * @param Request $request
     */
    public function __construct(UserProvider $provider, Request $request)
    
        $this->provider = $provider;
        $this->request = $request;
    


    /**
     * Determine if the current user is authenticated.
     *
     * @return bool
     */
    public function check()
    
        return !is_null($this->user);
    
    
    /**
     * Determine if the current user is a guest.
     *
     * @return bool
     */
    public function guest()
    
        return !$this->check();
    

    /**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user()
    
        if($this->check()) 
            return $this->user;
        

        $this->setUser(
            $this->provider->retrieveById(
                $this->request->session()->getId()
            )
        );

        return $this->user;
    

    /**
     * Get the ID for the currently authenticated user.
     *
     * @return int|null
     */
    public function id()
    
        return !is_null($this->user) ? $this->user->id : null;
    

    /**
     * Validate a user's credentials.
     *
     * @param  array $credentials
     * @return bool
     */
    public function validate(array $credentials = [])
    
        return false;
    

    /**
     * Set the current user.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable $user
     * @return void
     */
    public function setUser(Authenticatable $user)
    
        $this->user = $user;
    

user 方法中,您将会话 id 作为标识符传递,仅使用广播此方法是必要的。

4。在 AuthServiceProvider 中注册 Guard 和 UserProvider。

// app/Providers/AuthServiceProvider.php

   /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    
        $this->registerPolicies();

        Auth::provider('sessions', function (Application $app) 
            return new SessionUserProvider(
                $app->make('cache.store')
            );
        );

        Auth::extend('guest', function (Application $app, $name, array $config) 
            return new GuestGuard(Auth::createUserProvider($config['provider']), $app->make('request'));
        );
    

5.1 在config/auth.php中添加provider

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
        
        // New
        'sessions' => [
         'driver' => 'sessions',
         'model' => App\Models\Session::class,
        ],
    ],

5.2 在config/auth.php中添加保护

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],

        // New
        'guest' => [
            'driver' => 'guest',
            'provider' => 'sessions'
        ]
    ],

6。使用你的新后卫

// routes/channels.php

Broadcast::channel('chat.id', function (Authenticatable $user)
    return $user;
, ['guards' => ['guest']]);

请注意,您可以同时使用 'web' 作为守卫('web' 应该在 'guest' 之前)。它允许您找出谁是访客以及谁是登录用户 - 您可以在频道回调中检查 Authenticable 的实例。

And that how it looks in the laravel-echo-server database

【讨论】:

【参考方案5】:

在 Renan Coelho 的帮助下,我开始工作了。对我来说缺少的部分是使用以下内容覆盖 Broadcast::routes() 方法:

Route::post('/broadcasting/auth', function (Illuminate\Http\Request $req) 
    return Broadcast::auth($req);
);

Route::post('/broadcasting/auth'... 实际上是通过“Broadcast::routes()”方法添加的路由。这就是我们在这里覆盖它的原因。您可以通过在终端中输入php artisan route:list 来查看活动路线。

然后,Renan Coelho 已经说过,我必须添加一个自定义中间件 (AuthenticateGuest),为我创建一个随机用户。 (这是 hacky 部分)并将其添加到 kernel.php 中的 $middleware 数组中:

protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
        \Barryvdh\Cors\HandleCors::class,

        \App\Http\Middleware\AuthenticateGuest::class
    ];

AuthenticateGuest 中间件如下所示:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use App\User;

class AuthenticateGuest

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    
        Auth::login(factory(User::class)->make([
            'id' => (int)str_replace('.', '', microtime(true))
        ]));

        return $next($request);
    

希望对某人有所帮助,

塞巴斯蒂安

【讨论】:

【参考方案6】:

我的问题解决方案:

BroadcastServiceProvider.php (~/app/Providers/)

public function boot()

    if (request()->hasHeader('V-Auth'))  /* Virtual client. */
        Broadcast::routes(['middleware' => 'client_chat.broadcast.auth']);
     else 
        Broadcast::routes();
    

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

内核.php (~/app/Http/)

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    ...
    'client_chat.broadcast.auth' => \App\Http\Middleware\ClientChatBroadcasting::class,
];

ClientChatBroadcasting.php (~/app/Http/Middleware/)

public function handle($request, Closure $next)

    if (/** your condition **/) 
        $fakeUser = new User;
        $fakeUser->virtual_client = true;
        $fakeUser->id = /** whatever you want **/;
        $fakeUser->name = '[virtual_client]';
        $fakeUser->asdasdasdasdasd = 'asdasdasdasdasd';

        $request->merge(['user' => $fakeUser]);
        $request->setUserResolver(function () use ($fakeUser) 
            return $fakeUser;
        );
    

    return $next($request);

ChatChannel.php (~/app/Broadcasting/Chat/)

Broadcast::channel('chat.chatId', ChatChannel::class); Channel Classes

public function join($member/**($fakeUser)**/, $chatId)

    $memberData = [/** your data **/];

    /* If there is no member data (null), then there will be an authentication error. */
    return $memberData;

[放在你的 js 文件中,你想连接广播的地方]

this.Echo = new Echo(
        broadcaster: 'socket.io',
        host: /** your host **/,
        reconnectionAttempts: 60,
        encrypted: true,
        auth: 
            headers: 
                'V-Auth': true,
                'Access-Token': accessToken,
                'Virtual-Id': virtualId,
                'Chat-Id': chatId
            
        
    );

【讨论】:

以上是关于Laravel Echo - 允许访客连接到出席频道的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 https 在生产服务器上使用 Laravel Echo

Laravel 连接到 sql server

Laravel 回声回调

javascript 连接到Amazon Echo按钮

javascript 连接到Amazon Echo按钮

无法将 Laravel 连接到 MailChimp(laravel 5.4)