Я использую 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, но я нашел обходной путь.
В основном, что я должен был сделать:
- В моем контроллере зашифруйте ID пользователя и передайте его в javascript в качестве переменной.
- Передайте зашифрованную переменную ID через запрос Echo как часть заголовка.
- Измените функцию retrieveUser (), чтобы использовать find (Crypt :: decrypt ($ id)) для поиска пользователя вместо -> user () (или where (), что было странным образом запрещено).
Из того, что я могу сказать, это выглядит как приличная стратегия с точки зрения безопасности, но, возможно, читатели могли бы указать, действительно ли это правильно.
Чтобы проникнуть в частный канал, вам необходимо знать идентификатор канала, который вы хотите прослушать, а затем передать его в качестве зашифрованной переменной в заголовке запроса.
Возможно, потенциальный хакер мог бы сказать, что он / она хочет слушать частный канал «brand.1», и все, что им нужно будет сделать, это зашифровать номер 1 и пропустить его через заголовок. Думаю, я не знаю, как это работает достаточно, чтобы понять, возможно ли это.
В любом случае, мои цели сейчас:
- преобразование этого в настройку переопределения вместо явного изменения исходного кода Laravel.
- выяснить, достаточно ли безопасен для передачи зашифрованный идентификатор через заголовок запроса.
Похоже, что зашифрованный идентификатор в заголовке (который меняется каждый раз, когда вы запускаете запрос) более безопасен, чем просто пропуск через «api_token», который будет значением, хранящимся в таблице пользователей, и это то, что большинство людей кажется, делают.