node.js - PubNub安全组管理

标签 node.js pubnub

我们正在构建一个应用程序,我们决定使用pubnub作为通知和聊天的传输方式。
这是需求


用户应该能够直接在他们之间进行交流,
只有两个正在讨论的用户才能看到对话
我认为发布和订阅密钥都被破坏了
服务器应该能够为每个用户发送通知
用户应该只能看到他的通知


**在某个时候,我意识到您不能使用两组密钥发布到同一通道-一组密钥标识该通道-因此,服务器主密钥集和客户端无级别密钥是不可行的**

因此,最重要的是,使用pam,这是通知的流程

使用私钥的服务器会撤消全局级别上pub / sub密钥的任何权限
{读取:错误,写入:错误,管理:错误}
这样,任何人都无法使用密钥在全局级别上做任何事情

用户发送一个令牌-该令牌成为authKey-只有使用该auth密钥,用户才可以在通知通道上读取(称为“ notif- {userId}”)

现在服务器需要具有某种可发布到所有通道的masterKey-因此,逻辑上的事情是发出带有{读:true,写:true,manage:true,authKey:主密钥}
在这里我们失败-因为它回应“仅授权授权保留供将来使用”

现在,用于聊天的想法是创建一个频道名称“ chat- {userId1}-{userId2}”,并将此频道添加到组“ chats- {userId1}”和“ chats- {userId2}”中,然后添加到。 grant()基于令牌的每个用户频道的权限-授予成功但用户订阅了频道组-但向该组的实际发布失败并显示
状态 : {
   类别:“ PNAccessDeniedCategory”,
   操作:“ PNPublishOperation”
}

这是复制问题的代码示例

    'use strict';

const pubnubConf = require('../config/pubnub-master');

const PubNub = require('pubnub');
const masterKeys = pubnubConf.getMasterKeys();
const pubnub = new PubNub(masterKeys);
const pubnubSecret = new PubNub(pubnubConf.getSecretKeys());

const token = 'lklkdjwdq';

masterKeys.authKey = token;
const pubnubClient = new PubNub(masterKeys);
const userAGroup = 'chats-aaa';
const userBGroup = 'chats-bbb';
const chat = 'chat-aaa-bbb';


function _invalidateServerKeys () {
    return new Promise((resolve, reject) => {
        pubnubSecret.grant({
            read: false,
            write: false,
            manage: false,
            ttl: 0
        }, (status, response) => {
            if (status.error) {
                reject(status);
            }
            resolve(status);
        });
    });
}


function _setMasterKeyForChannelGroup () {
    return new Promise((resolve, reject) => {
        pubnubSecret.grant({
            read: true,
            write: true,
            manage: true,
            channels: [chat],
            channelGroups: [userAGroup, userBGroup],
            authKeys: [pubnubConf.getMasterKey()],
            ttl: 0
        }, (status, response) => {
            if (status.error) {
                this._logger.fatal({
                    status: status,
                    response: response
                });
                reject(status);
            }
            resolve(status);
        });
    });
}

function _addChanelsToGroup () {
    return new Promise((resolve, reject) => {
        pubnub.channelGroups.addChannels({
            channels: [chat],
            channelGroup: [userBGroup]
        }, status => {
            if (status.error) {
                this._logger.fatal(status);
                return reject();
            }
            resolve(status);
        });
    });
}

function _addTokenPermission () {
    return new Promise((resolve, reject) => {
        pubnubSecret.grant({
            channelGroups: [userBGroup],
            authKeys: [token],
            ttl: 65,
            read: true,
            write: true,
            manage: false
        }, (status, response) => {
            if (status.error) {
                return reject({
                    status: status,
                    response: response
                });
            }
            resolve();
        });
    });
};

function _subscribeToGroup () {
    return new Promise((resolve, reject) => {
        pubnubClient.addListener({
            status: statusEvent => {
                if (statusEvent.error) {
                    reject(statusEvent);
                    return;
                }
                resolve(statusEvent);
            },
            message: message => {
            }
        });
        pubnubClient.subscribe(
            {channelGroups: [userBGroup]});
    });
}

function _publishToGroup () {
    pubnubClient.publish(
        {
            message: {
                such: 'object'
            },
            channel: chat,
            storeInHistory: false
        },
        function (status, response) {
            if (status.error) {
                // handle error
                console.log(status)
            } else {
                console.log("message Published w/ timetoken", response.timetoken)
            }
        }
    );
}

_invalidateServerKeys()
    .then(_setMasterKeyForChannelGroup)
    .then(_addChanelsToGroup)
    .then(_addTokenPermission)
    .then(_subscribeToGroup)
    .then(_publishToGroup);




const masterAuthKey = 'qdwqqdwdqwqdwqdw';


module.exports = {
    getSecretKeys: () => ({
        ssl: true,
        logVerbosity: true,
        publishKey: 'pub-c-',
        subscribeKey: 'sub-c-',
        secretKey: 'sec-c-'
    }),
    getMasterKeys: () => ({
        ssl: true,
        logVerbosity: true,
        publishKey: 'pub-c-',
        subscribeKey: 'sub-c-',
        authKey: masterAuthKey
    }),
    getMasterKey: () => (masterAuthKey)
};


当然,仅auth的PAM可以解决该问题-但它似乎不可用
另一种方法是管理每个通道的服务器端密钥-但这很浪费。

最佳答案

PubNub Access Manager最佳做法

这里有很多内容,所以我将仅解决错误或不太准确或可以通过更好的方法完成的事情。

撤销全局级别的权限

启用Access Manager时的默认设置是撤消所有人的所有权限。您在这里做什么:pubnubSecret.grant({read: false, write: false, manage: false, ttl: 0},只是撤销可能已在子密钥(应用程序)级别授予的所有权限,但不会撤销任何身份验证密钥或通道级别的权限(它们本质上是分层的,类似于CSS优先,但相反)。但这是无害的,实际上是一种很好的安全保护措施,以防万一有人(内部开发人员)意外授予子密钥级别。

服务器需要某种masterKey

您只授予一个auth-key所遇到的问题是预期的行为,但是我看到了您想要的-全局根访问权限auth-key。当前,您要么为通道的auth-key授予权限,要么为no auth-key授予权限,但是您不能为auth-key授予任何权限(无通道或通道组)权限。但是您可以在通道级别(无身份验证密钥)或子密钥级别(无通道且没有身份验证密钥)授予访问权限,以便任何人都可以在没有身份验证密钥的情况下进行访问(如上所述)。

授予root用户执行所有操作的权限是一项缺少的功能,将来会添加该功能。现在,您必须执行两个不同的授予操作,以使服务器拥有一个auth-key,该密钥可以允许服务器读取,写入和管理所有内容,而无需授予创建的每个新通道的权限。

通配符频道组管理

首先,将通配符权限授予服务器的auth-key,以使用通配符(:)作为通配符来管理所有通道组(不要问,但这与通道组以前拥有的已弃用的名称空间有关)。

pubnubSecret.grant({authKey: serverAuthKey, channelGroups:[':'], manage: true, ttl: 0}


就是这样,您的服务器现在可以将频道添加/删除到您创建的任何频道组中。

通配符通道读写

对于通道,这有点不同,因为没有对所有通道通配符的授权读/写,就像管理通道组一样(至少不是在根级别)。我的意思是,如果您对所有通道名称(例如notif.)使用通道前缀,那么通道将具有诸如notif.user123notif.user326之类的名称,那么您可以授予您的读/写权限通道notif.*上的服务器的身份验证密钥。

pubnubSecret.grant({channels:['notif.*'], read: true, write: true, ttl: 0}


现在,这意味着如果您有任何不带notif.*前缀的通道,则它们将不属于此通配符授予。而且您不能授予notif.user123.*。仅位于通配符名称的第二级/段。

聊天频道和频道组

您说您正在创建一个唯一的渠道,供两个用户互相交谈:chat-{userId1}-{userId2}。根据上述建议,在服务器上需要notif.前缀,以便您的服务器具有这种访问权限。

并且您表明您正在为每个用户创建频道组并将该频道添加到每个用户的频道组,这将允许每个用户接收发布到该频道的消息。但是您错误地或错误地说您正在尝试publish频道组,但是频道组仅用于订阅,而不用于发布(即您不能publish到频道组)。永远不要将频道组上的manage授予用户,因为这将允许恶意用户将任何频道添加到该频道组中并具有对该频道的read访问权限。

正确的做法是:


仅将manage授予服务器的通道组-您已经使用通配符通道组授予和冒号通配符完成了此操作-完成。
read授予每个用户在其自己的专用频道组上的访问权限:chats-{userId1}chats-{userId2}等,并且每个用户将订阅自己的频道组。
服务器将生成chat-user1-user2频道,并将该频道添加到每个用户的频道组中,然后他们将立即订阅新的聊天频道。
服务器将在write通道上为每个用户的身份验证密钥chat-user1-user2auth-key-user1授予auth-key-user1权限。您可以将同一组通道的相同权限同时授予多个auth-key。


摘要


使用':'为服务器的通道组通配符授予manage
使用服务器的read/write通道将通配符授予notif.*到所有通道
read授予用户专用频道组中的每个用户
write授予两个用户的共享聊天频道
将共享的聊天频道添加到每个用户的私人频道组


我认为这几乎可以解决您遇到的所有问题,但是如果有任何不清楚的地方,无法解决您的问题或还有其他问题,请告诉我。

对评论中问题的回答

是“。”频道名称中允许使用?

是的,它不是正式无效的,而是保留的。因此,注意事项是不要在不考虑通配符含义(授予和订阅)的情况下,将点字符用作定界符。这就是为什么我在用例中推荐点的原因,以便您可以授予notifi.*权限,使服务器对使用该命名约定创建的所有通道都具有读/写访问权限。以下是有关保留/无效字符的其他一些详细信息。

通道和通道组名称与UTF-8兼容,并且长度限制为92个字符。保留的字符不应用作通道或通道组名称(但在某些情况下可以成功使用)。


期间:“。” (可以用于频道,但不能用于频道组;仅应在计划使用通配符授予和/或wildcard subscribe的频道中使用)
斜线:“ /”
反斜杠:“ \”
逗号:“,”
冒号:“:”
空间: ' '


是否有生成频道名称的命令?

不,但是生成(一个词可能太强了)我的意思是您的服务器是创建通道名称的服务器。这几乎是必须的,因为它需要知道该名称是什么,以便为客户端的身份验证密钥授予write权限,以便在其上发送publish消息。服务器也需要将频道添加到用户的频道组中。因此,它也可能是创建通道名称的那个。

频道名称的生成方式取决于您。通常,开发人员仅使用UUID生成器来创建动态通道名称。我认为您要使用可预测的频道名称,以便每个客户端都可以通过知道与之聊天的另一方的用户名轻松地知道频道名称是什么。只需确保按字典顺序对用户名进行排序即可,这样就不会倒退它们:chat-userabc-userxyz而不是chat-userxyz-userabc(您可能已经想通了,但是为了所有阅读的内容都想提及它这个)。如果您希望服务器具有自动的notif.read访问权限(基于对write推荐的通配符授予),请不要在通道名称前添加notif.*前缀。

关于node.js - PubNub安全组管理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38785942/

相关文章:

node.js - 使用 NODE-RED 连接外部 MQTT 发布者

javascript - JWT jsonwebtoken在node.js中没有过期

mqtt - 将 MQTT 与 PubNub 结合使用

parse-platform - 带存储的 Pubnub 聊天应用程序

mysql - 选择中的计数子查询 - mongoDB

javascript - 处理数据并记录来自请求的人

javascript - 如何将实时模型附加到 angular.js 中的 Controller ?

swift - 使用未声明的类型 PNObjectEventListener

node.js - NodeJS 缓冲区拆分、字符串和二进制

pubnub - 获取与 PubNub 订阅 key 关联的所有 channel