我正在使用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获得与请求一起发送的用户信息,但是我确实想出了一种解决方法。
基本上我要做的事情:
据我所知,从安全角度看,这似乎是一个不错的策略,但也许读者可以指出这是否正确。
要侵入专用频道,您必须知道您要收听的频道的ID,然后将其作为加密的变量传递到请求的标头中。
也许潜在的黑客可能会说他/她想收听私人频道“ brand.1”,而他们所要做的就是加密数字1并将其通过标头。我想我不知道该怎么做才能知道这是否可能。
无论如何,我现在的目标是:
似乎标头中的加密ID(每次运行请求时都会更改)比简单地传递“ api_token”(大多数用户将其存储在用户表中)的安全性高似乎可以。