使用推送通知订阅对象的“ p256dh”或“端点”键值作为后端内的标识符是否安全?

问题描述

用户通过PushManager.subscribe()创建预订时,给定applicationServerKeyvapid公共密钥),它将创建一个有效的预订,其内容可以发送并存储在数据库中。发送到数据库内容如下所示:

{"endpoint":"https://fcm.googleapis.com/fcm/send/dpH5lCsTSSM:APA91bHqjZxM0VImWWqDRN7U0a3AycjUf4O-byuxb_wJsKRaKvV_iKw56s16ekq6FUqoCF7k2nICUpd8fHPxVTgqLunFeVeB9lLCQZyohyAztTH8ZQL9WCxKpA6dvTG_TUIhQUFq_n","keys": {
    "p256dh":"BLQELIDm-6b9Bl07YrEuXJ4BL_YBVQ0dvt9NQGGJxIQidJWHPNa9YrouvcQ9d7_MqzvGS9Alz60SZNCG3qfpk=","auth":"4vQK-SvRAN5eo-8ASlrwA=="
    }
}

来源:Introduction to push notifications

此对象提供了唯一的端点,后端服务器在该端点中知道要向其发送推送通知调用位置,并使用相应的vapid私钥进行身份验证。

Google声明此端点的唯一标识符是不透明的,因此无法确定个人数据。

标识符是不透明的。作为开发人员,您无法确定任何 个人数据。而且,它不稳定,所以不能用来 跟踪用户

Surya Sankar开始本教程之后,可以将整个订阅对象发送到服务器,并存储在数据库中,以供以后在将推送通知发送到关联的浏览器/设备时使用。但是,在将条目记录到数据库之前,它会进行比较以查看是否已经记录了具有相同内容的条目,而不创建重复项。

@app.route("/api/push-subscriptions",methods=["POST"])
def create_push_subscription():
    json_data = request.get_json()
    subscription = PushSubscription.query.filter_by(
        subscription_json=json_data['subscription_json']
    ).first()
    if subscription is None:
        subscription = PushSubscription(
            subscription_json=json_data['subscription_json']
        )
        db.session.add(subscription)
        db.session.commit()
    return jsonify({
        "status": "success"
    })

来源:push_subscriptions_api.py

由于整个订阅对象与webpush一起使用来触发推送通知,因此我了解到整个订阅对象将需要发送到服务器。

from pywebpush import webpush,WebPushException
import json
from flask import current_app


def trigger_push_notification(push_subscription,title,body):
    try:
        response = webpush(
            # Uses entire subscription object stored in database as parameter
            subscription_info=json.loads(push_subscription.subscription_json),data=json.dumps({"title": title,"body": body}),vapid_private_key=current_app.config["vapid_PRIVATE_KEY"],vapid_claims={
                "sub": "mailto:{}".format(
                    current_app.config["vapid_CLaim_EMAIL"])
            }
        )
        return response.ok
    except WebPushException as ex:
        if ex.response and ex.response.json():
            extra = ex.response.json()
            print("Remote service replied with a {}:{},{}",extra.code,extra.errno,extra.message
                  )
        return False


def trigger_push_notifications_for_subscriptions(subscriptions,body):
    return [trigger_push_notification(subscription,body)
            for subscription in subscriptions]

来源:webpush_handler.py

这遵循该库(pywebpush)文档中的指南,该指南规定在其README.md文件Usage部分下进行以下操作。

因此,我很好奇订阅对象的哪些部分包含敏感信息(即"auth")? PushSubscription.getKey()的{​​{3}}指出{ {1}}是身份验证密钥,"auth"是公共密钥。有了推送documentation,客户端和服务器之间的通信将是安全的。

当尝试从前端引用此订阅时,我可以将该订阅对象的哪一部分用作标识符以在数据库中引用该条目?

(即对此端点的一个"p256dh"请求看起来像下面的GET

我认为我无法将整个订阅对象用作此终结点的标识符,因为它将公开https:/example.com/client/push-subscription/<subscription-object-identifier>下的内容我可以使用"auth""endpoint"下的内容作为标识符,因为它们是唯一的并且不会暴露任何敏感数据吗?

解决方法

您可以安全地将endpoint用作数据库和后端调用中的唯一标识符。

自2015年以来,我们一直在Pushpad Javascript SDK中使用它,没有任何问题。

例如,当您从Javascript调用到后端以存储该特定订阅的数据时,可以将端点用作标识符。

一个端点可以被认为是唯一且秘密的(因为它包含一个长随机令牌)。

现在是正确的,在标准开始时也是正确的...过去keys和有效负载都不存在,仅endpoint用于所有事物,包括从应用程序服务器中获取新的通知。

对于keys,我只是将它们在数据库中保密,而不使用它们作为标识符。