Как пройти аутентификацию, чтобы присоединиться к частному каналу, используя Laravel Echo и Socket.io - PullRequest
0 голосов
/ 17 мая 2019

Я использую Laravel 5.8.10, React 16.8, Laravel Echo Server 1.5.2, Redis 3.2.9 и Socket.io 2.2.0.

Я НЕ использую Pusher и не хочуиспользовать Pusher.

Я пытаюсь создать базовую систему чата для пользователей сайта.Они обычно входят в систему, используя сеансовую аутентификацию с электронной почтой и паролем - все это прекрасно работает.

Существует 2 типа пользователей: бренды и поставщики.Каждый из них имеет свою собственную охрану (веб-бренды и веб-влияния).Все сессионные охранники работают нормально.

Я создаю страницу чата с помощью React. Я могу успешно присоединиться к общедоступному каналу и получать сообщения на этом общедоступном канале.Однако проблема заключается в том, что я пытаюсь сделать канал закрытым.

Когда я пытаюсь присоединиться к частному каналу, Laravel Echo Server отправляет запрос аутентификации на: http://localhost:8000/broadcasting/auth.

Но это возвращает следующую ошибку 401:

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

Прямо сейчас я пытаюсь аутентифицировать запросы к / broadcasting / auth, используя простой 'api_token', который хранится в таблицах пользователей (бренды и факторы влияния являются2 пользовательских таблицы).Это уникальная строка из 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: маршрут использует auth: api middleware:

| 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,
        ],
    ],

];

Вот мои каналы.php:

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

Обратите внимание, что у меня естьbrand. {id} по умолчанию возвращает true.Я также попробовал это для channel.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": ""
    }
}

Возможно, я не отправляюПравильно ли указан api_token в заголовке эхо-запроса laravel?

ОБНОВЛЕНИЕ / РЕДАКТИРОВАНИЕ:

Теперь я попытался удалить промежуточное ПО auth: api для маршрута / broadcasting / auth,Я не уверен, что это было правильно.

Это теперь выдает ошибку 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 (), что было странным образом запрещено).

Из того, что я могу сказать, это выглядит как приличная стратегия с точки зрения безопасности, но, возможно, читатели могли бы указать, действительно ли это правильно.

Чтобы проникнуть в частный канал, вам необходимо знать идентификатор канала, который вы хотите прослушать, а затем передать его в качестве зашифрованной переменной в заголовке запроса.

Возможно, потенциальный хакер мог бы сказать, что он / она хочет слушать частный канал «brand.1», и все, что им нужно будет сделать, это зашифровать номер 1 и пропустить его через заголовок. Думаю, я не знаю, как это работает достаточно, чтобы понять, возможно ли это.

В любом случае, мои цели сейчас:

  1. преобразование этого в настройку переопределения вместо явного изменения исходного кода Laravel.
  2. выяснить, достаточно ли безопасен для передачи зашифрованный идентификатор через заголовок запроса.

Похоже, что зашифрованный идентификатор в заголовке (который меняется каждый раз, когда вы запускаете запрос) более безопасен, чем просто пропуск через «api_token», который будет значением, хранящимся в таблице пользователей, и это то, что большинство людей кажется, делают.

...