Downloadable-Produkte in Magento 2: Lizenzen, Link-Sicherheit, Auslieferung
AI generated
M2
di.xml
Magento 2 · Downloadable Products · Sicherheit · PHP 8.4
Downloadable-Produkte in Magento 2
Lizenzen, Link-Sicherheit und sichere Auslieferung

Wer in Magento 2 Software-Lizenzen, E-Books oder Whitepaper über Downloadable-Produkte verkauft, muss weit mehr absichern als nur den Checkout: link_url_key-Hashing, Download-Limits, Order-Status-Gating und eine Auslieferung, die niemals den rohen Dateipfad preisgibt, entscheiden darüber, ob digitale Güter geschützt oder frei kopierbar im Netz landen.

18 Min. Lesezeit Datenmodell · Link-Sicherheit · Signierte URLs · Lizenzpool Magento 2.4.8-p4 · PHP 8.4 · Hyva

1. Wann Downloadable-Produkte in Magento 2 sinnvoll sind

Ein Downloadable-Produkt ist der eigene Produkttyp in Magento 2 für digitale Güter, die nach dem Kauf als Datei ausgeliefert werden: Software-Lizenzen, E-Books, Musikdateien, Whitepaper oder Vorlagen. Anders als beim Virtual Product, das ausschließlich eine Leistung ohne physische oder digitale Zustellung abbildet, transportiert das Downloadable-Produkt tatsächliche Dateien über eine eigene Download-Infrastruktur mit Zugriffskontrolle. Anders als beim Simple Product entfällt Versand und Lager, dafür kommt eine ganz eigene Klasse von Risiken hinzu: Wer einmal Zugriff auf eine Datei hat, kann sie beliebig oft kopieren und weiterverteilen, wenn der Shop keine technischen Schranken einzieht.

Das Geschäftsmodell hinter Downloadable-Produkten ist meist lizenzbasiert statt stückbasiert: Der Kunde kauft nicht die Datei an sich, sondern ein Nutzungsrecht, das durch einen Lizenzschlüssel, eine begrenzte Anzahl an Downloads oder eine zeitliche Gültigkeit eingeschränkt wird. Das unterscheidet den kommerziellen Einsatz deutlich vom naiven Anwendungsfall, bei dem einfach eine Datei hinter den Checkout gehängt wird. Wer Software-Lizenzen oder kostenpflichtige E-Books verkauft, muss die technische Umsetzung von Anfang an auf Kontrolle statt auf reine Bereitstellung auslegen.

Für Agenturen bedeutet das: Die Standardkonfiguration von Magento 2 für Downloadable-Produkte deckt die Grundlagen ab, Link-Speicherung, Sample-Verwaltung, Download-Zähler, reicht aber bei sensiblen digitalen Gütern nicht aus. Wer Software-Lizenzen mit Aktivierungsschlüsseln verkauft oder urheberrechtlich geschütztes Material ausliefert, muss die in diesem Artikel beschriebenen Schutzmechanismen zusätzlich implementieren: sichere Link-Generierung, konsequentes Order-Status-Gating und eine Auslieferung, die niemals den rohen Dateipfad im Browser sichtbar macht.

2. Datenmodell: downloadable_link, downloadable_sample, downloadable_link_purchased

Magento 2 registriert Downloadable-Produkte über den Produkttyp downloadable in Magento\Downloadable\Model\Product\Type, der neben den Standard-EAV-Attributen aus catalog_product_entity zwei eigene Tabellenfamilien mitbringt: eine für Links, eine für kostenlose Samples. Die Tabelle downloadable_link speichert die eigentlichen kaufpflichtigen Ressourcen, jede Zeile referenziert über product_id ein Produkt und enthält Spalten wie link_url, link_file, link_type, number_of_downloads und link_url_key. Die Tabelle downloadable_sample ist strukturell fast identisch, bildet aber Vorschau-Dateien ab, die ohne Kauf öffentlich zugänglich sind, etwa eine Leseprobe eines E-Books.

Sobald eine Bestellung mit Downloadable-Produkten aufgegeben wird, entsteht eine Zeile in downloadable_link_purchased, verknüpft mit der zugehörigen sales_order_item über order_item_id und mit dem Order-Datensatz über order_id. Diese Tabelle trägt Felder wie link_hash, number_of_downloads_bought und status, also den Kaufkontext, nicht das Produkt selbst. Erst die Kindtabelle downloadable_link_purchased_item verknüpft den Kauf mit dem konkreten Link aus downloadable_link über link_id und führt pro Item einen eigenen Zähler number_of_downloads_used.

Diese Trennung in drei Ebenen, Produktdefinition, Bestellkontext, Nutzungszähler, ist kein Zufall, sondern erlaubt es, denselben Link in mehreren Bestellungen unabhängig zu tracken. Zwei Kunden, die dasselbe Downloadable-Produkt kaufen, bekommen unterschiedliche link_hash-Werte in downloadable_link_purchased, obwohl beide auf dieselbe Zeile in downloadable_link zeigen. Damit lässt sich Missbrauch pro Bestellung isolieren, ohne dass ein kompromittierter Link automatisch alle Käufer betrifft.

Der Kern der Link-Sicherheit bei Downloadable-Produkten ist, dass niemals die reale Dateiposition im Frontend sichtbar wird. Magento erzeugt für jeden Link über Magento\Downloadable\Helper\File::generateKey() einen zufälligen, kryptografisch ausreichend langen Hash, der als link_url_key beziehungsweise nach dem Kauf als link_hash in downloadable_link_purchased gespeichert wird. Der Download-Controller löst diesen Hash serverseitig gegen die reale Datei auf, der Kunde sieht im Browser ausschließlich die Hash-URL, niemals den Pfad unterhalb von var/downloadable/files/ oder ein extern gehosteter Klartext-Link.

Magento unterscheidet zwei link_type-Werte mit unterschiedlichen Sicherheitsimplikationen: file speichert die Datei innerhalb der Magento-Dateiablage unter pub/media/downloadable/files/, wodurch der komplette Zugriffsschutz über den Controller läuft. url verweist stattdessen auf eine externe Ressource, was praktisch für CDN-gehostete Assets sein kann, aber ein Risiko birgt: Liegt die externe URL ungeschützt vor, kann sie unabhängig vom Magento-Zugriffsschutz direkt aufgerufen werden. Für lizenzpflichtige Software-Downloads ist link_type = file mit serverseitig kontrolliertem Zugriff daher fast immer die richtige Wahl.

Ein häufiger Fehler in Individualentwicklungen ist, den Pfad unterhalb von pub/media/downloadable direkt per Webserver-Konfiguration erreichbar zu lassen, etwa weil ein Reverse-Proxy oder eine CDN-Regel den kompletten media-Pfad öffentlich cacht. Der link_url_key-Mechanismus bringt nur dann Sicherheit, wenn der physische Dateipfad selbst niemals direkt aufrufbar ist, sondern ausschließlich über den kontrollierten Download-Controller erreichbar bleibt. Ein Webserver-Deny-Rule auf das Verzeichnis downloadable/files gehört deshalb zu jedem produktiven Setup mit Downloadable-Produkten dazu.

4. Zugriffskontrolle: Order-Status-Gating und Download-Limits

Magento gibt einen Download für Downloadable-Produkte erst frei, wenn die zugehörige Bestellung einen bestimmten Status erreicht hat, standardmäßig konfigurierbar über Magento_Downloadable/etc/config.xml mit der Option "Order Item Status to Enable Downloads" (Pending oder Invoiced). Erst wenn eine Rechnung existiert oder die Order als abgeschlossen markiert ist, wechselt der Status des zugehörigen Purchased\Item-Datensatzes und der Download-Link wird im Kundenkonto freigeschaltet. Vor diesem Zeitpunkt liefert der Controller konsequent einen Fehler, selbst wenn der Angreifer den korrekten Hash erraten hätte.

Die zweite Schutzebene ist number_of_downloads, das pro Link konfigurierbare Limit, wie oft eine Datei heruntergeladen werden darf, mit dem Sonderwert 0 für unbegrenzt. Jeder erfolgreiche Download inkrementiert number_of_downloads_used in downloadable_link_purchased_item, und der Controller prüft vor jeder Auslieferung, ob dieser Zähler das Limit bereits erreicht hat. Für Software-Lizenzen mit begrenzten Aktivierungen ist ein niedriges, explizites Limit sinnvoller als die Standard-Unbegrenztheit, weil es das Weiterverteilen von Downloads über den eigenen Kundenkreis hinaus technisch erschwert.

Wer eigene Prüfungen vor dem Ausliefern ergänzen will, etwa eine zusätzliche IP-Beschränkung oder eine Kopplung an einen Lizenzpool, sollte nicht den Core-Controller patchen, sondern einen eigenen Service Contract vorschalten, der den Status aus Magento\Downloadable\Model\Link\Purchased\Item ausliest, bevor irgendein Dateizugriff stattfindet.


declare(strict_types=1);

namespace Mironsoft\DownloadGuard\Model;

use Magento\Downloadable\Model\Link\Purchased\Item;
use Magento\Downloadable\Model\Link\PurchasedFactory;
use Magento\Downloadable\Model\ResourceModel\Link\Purchased\Item\CollectionFactory;
use Magento\Framework\Exception\LocalizedException;

/**
 * Service Contract that validates access to a purchased downloadable link
 * before the delivery controller is allowed to stream any file.
 */
class DownloadAccessValidator
{
    /**
     * @param CollectionFactory $itemCollectionFactory Factory for purchased-item collections
     * @param PurchasedFactory $purchasedFactory Factory for the purchased-link aggregate
     */
    public function __construct(
        private readonly CollectionFactory $itemCollectionFactory,
        private readonly PurchasedFactory $purchasedFactory,
    ) {
    }

    /**
     * Validates that the given link hash may currently be downloaded.
     *
     * @param string $linkHash The public, non-guessable hash from the request URL
     * @return Item The validated purchased-item entity
     * @throws LocalizedException If the order status forbids access or the limit is reached
     */
    public function validate(string $linkHash): Item
    {
        $collection = $this->itemCollectionFactory->create();
        $collection->addFieldToFilter('link_hash', ['eq' => $linkHash]);
        /** @var Item|null $item */
        $item = $collection->getFirstItem();

        if (!$item || !$item->getId()) {
            throw new LocalizedException(__('Invalid download link.'));
        }

        if ($item->getStatus() !== Item::LINK_STATUS_AVAILABLE) {
            throw new LocalizedException(__('This download is not available yet.'));
        }

        $limit = (int) $item->getNumberOfDownloadsBought();
        $used = (int) $item->getNumberOfDownloadsUsed();

        if ($limit > 0 && $used >= $limit) {
            throw new LocalizedException(__('Download limit reached for this item.'));
        }

        return $item;
    }
}

5. Downloadable-Produkte programmatisch anlegen

Für Datenmigrationen, Import-Skripte oder Setup-Patches, die Downloadable-Produkte automatisiert anlegen, sind ProductRepositoryInterface und Magento\Downloadable\Api\Data\LinkInterfaceFactory die richtigen Service Contracts. Statt das Produkt-Model direkt zu instanziieren, arbeitet man ausschließlich über die Repository-Schicht, wodurch Indexer-Invalidierung, Event-Dispatching und Plugin-Ketten korrekt greifen, genau wie bei jedem anderen Produkttyp auch.

Der Produkttyp muss explizit auf downloadable gesetzt werden, und die Links werden nicht direkt gespeichert, sondern über das downloadable_link_data-Extension-Attribute am Produkt übergeben, bevor save() aufgerufen wird. Diese Trennung zwischen Produktbasisdaten und typenspezifischen Downloadable-Daten spiegelt exakt das Datenmodell aus Abschnitt 2 wider: Erst wird das Produkt in catalog_product_entity persistiert, dann verarbeitet ein Plugin auf der Repository-Ebene die Extension-Attribute und schreibt die zugehörigen Zeilen in downloadable_link.


declare(strict_types=1);

namespace Mironsoft\DownloadableImport\Model;

use Magento\Catalog\Api\Data\ProductInterfaceFactory;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product\Type as ProductType;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Downloadable\Api\Data\LinkInterfaceFactory;
use Magento\Downloadable\Api\Data\ProductAttributeInterfaceFactory;
use Magento\Framework\Exception\CouldNotSaveException;

/**
 * Creates a downloadable product with one purchasable link via Service Contracts.
 */
class LicenseProductCreator
{
    /**
     * @param ProductInterfaceFactory $productFactory Factory for the base product entity
     * @param ProductRepositoryInterface $productRepository Repository used to persist products
     * @param LinkInterfaceFactory $linkFactory Factory for downloadable link data objects
     * @param ProductAttributeInterfaceFactory $extensionFactory Factory for downloadable extension attributes
     */
    public function __construct(
        private readonly ProductInterfaceFactory $productFactory,
        private readonly ProductRepositoryInterface $productRepository,
        private readonly LinkInterfaceFactory $linkFactory,
        private readonly ProductAttributeInterfaceFactory $extensionFactory,
    ) {
    }

    /**
     * Creates a downloadable license product with the given SKU and file path.
     *
     * @param string $sku Unique product SKU
     * @param string $name Product name shown in the storefront
     * @param string $filePath Absolute path to the license file on disk
     * @return int The number of downloads granted per purchase
     * @throws CouldNotSaveException If the product or its links cannot be persisted
     */
    public function create(string $sku, string $name, string $filePath, int $downloadsAllowed = 3): int
    {
        $product = $this->productFactory->create();
        $product->setTypeId(ProductType::TYPE_DOWNLOADABLE)
            ->setAttributeSetId(4)
            ->setSku($sku)
            ->setName($name)
            ->setVisibility(Visibility::VISIBILITY_BOTH)
            ->setStatus(1)
            ->setPrice(199.00);

        $link = $this->linkFactory->create();
        $link->setTitle($name . ' - License File')
            ->setLinkType('file')
            ->setLinkFile($filePath)
            ->setNumberOfDownloads($downloadsAllowed)
            ->setSortOrder(1)
            ->setIsShareable(0);

        $extensionAttributes = $this->extensionFactory->create();
        $extensionAttributes->setDownloadableProductLinks([$link]);
        $product->setExtensionAttributes($extensionAttributes);

        $savedProduct = $this->productRepository->save($product);

        return (int) $downloadsAllowed;
    }
}

6. Sichere Auslieferung: Custom Controller mit Signed URLs

Der Standard-Download-Controller von Magento löst den Hash auf und liefert die Datei über readfile(), das reicht für die meisten Shops. Bei sensiblen Downloadable-Produkten, etwa Software mit hohem Schwarzmarktwert, lohnt sich eine zusätzliche Schutzschicht: eine signierte, zeitlich begrenzte URL, die zusätzlich zum bestehenden Hash geprüft wird. Der Link im Kundenkonto verweist dann nicht mehr dauerhaft gültig, sondern nur für ein enges Zeitfenster, etwa 15 Minuten, wonach eine neue signierte URL angefordert werden muss.

Die Signatur wird als HMAC über Link-ID, Ablaufzeitstempel und einen serverseitigen Secret-Key gebildet und als Query-Parameter an die URL gehängt. Der Controller verifiziert vor jeder Auslieferung sowohl die Signatur als auch, ob der Zeitstempel noch in der Zukunft liegt. Für die eigentliche Dateiübertragung ist X-Sendfile beziehungsweise das Nginx-Äquivalent X-Accel-Redirect effizienter als readfile() in PHP, weil der Webserver die Datei direkt vom Dateisystem an den Client streamt, ohne den kompletten Inhalt durch den PHP-Prozess zu puffern.

Hotlinking, also das direkte Verlinken der Download-URL von fremden Seiten aus, wird zusätzlich über die Prüfung des Referer-Headers sowie durch die kurze Gültigkeitsdauer der Signatur unterbunden: Selbst wenn eine URL öffentlich geteilt wird, verliert sie nach Ablauf des Zeitfensters ihre Gültigkeit, und ein Dritter müsste sich erneut über das Kundenkonto authentifizieren, um eine neue signierte URL zu erhalten.


declare(strict_types=1);

namespace Mironsoft\DownloadGuard\Controller\Download;

use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\RawFactory;
use Magento\Framework\Exception\LocalizedException;
use Mironsoft\DownloadGuard\Model\DownloadAccessValidator;
use Mironsoft\DownloadGuard\Model\SignedUrlValidator;

/**
 * Streams a purchased downloadable file only if the signed URL is still valid
 * and the order-status gate in DownloadAccessValidator allows access.
 */
class SecureFile implements HttpGetActionInterface
{
    /**
     * @param RequestInterface $request Current HTTP request
     * @param RawFactory $rawResultFactory Factory for raw binary responses
     * @param DownloadAccessValidator $accessValidator Order-status and limit gate
     * @param SignedUrlValidator $signedUrlValidator Validates the HMAC signature and expiry
     */
    public function __construct(
        private readonly RequestInterface $request,
        private readonly RawFactory $rawResultFactory,
        private readonly DownloadAccessValidator $accessValidator,
        private readonly SignedUrlValidator $signedUrlValidator,
    ) {
    }

    /**
     * Executes the secure download action.
     *
     * @return ResponseInterface|\Magento\Framework\Controller\Result\Raw
     * @throws LocalizedException If the signature is invalid or expired
     */
    public function execute()
    {
        $hash = (string) $this->request->getParam('hash');
        $signature = (string) $this->request->getParam('sig');
        $expires = (int) $this->request->getParam('exp');

        $this->signedUrlValidator->assertValid($hash, $signature, $expires);
        $item = $this->accessValidator->validate($hash);

        $filePath = $item->getLinkFilePath();

        $result = $this->rawResultFactory->create();
        $result->setHeader('Content-Type', 'application/octet-stream');
        $result->setHeader('Content-Disposition', 'attachment; filename="' . basename($filePath) . '"');
        // Offload the actual transfer to the webserver instead of buffering in PHP
        $result->setHeader('X-Accel-Redirect', '/protected-downloads/' . rawurlencode(basename($filePath)));

        return $result;
    }
}

7. Lizenzschlüssel-Verwaltung: Eigenes Modul für einen Lizenzpool

Sobald Downloadable-Produkte echte Softwarelizenzen abbilden, reicht die reine Dateiauslieferung nicht mehr aus, es braucht einen individuellen Lizenzschlüssel pro Bestellposition. Das passende Muster ist ein eigenes Modul mit einer Lizenzpool-Tabelle, deklariert über db_schema.xml, in der vorab generierte Schlüssel als "verfügbar" markiert vorliegen. Ein Observer auf sales_order_place_after reserviert dann pro gekauftem Downloadable-Produkt genau einen freien Schlüssel aus dem Pool und verknüpft ihn fest mit der jeweiligen order_item_id.

Wichtig für die Datenintegrität ist, die Zuweisung transaktional und mit einer eindeutigen Sperre auf Datenbankebene abzusichern, etwa über SELECT ... FOR UPDATE beim Auslesen des nächsten freien Schlüssels, damit bei parallelen Bestellungen niemals derselbe Lizenzschlüssel zweimal vergeben wird. Die Fremdschlüsselbeziehung zu sales_order_item stellt zusätzlich sicher, dass ein Schlüssel automatisch als storniert markiert werden kann, wenn die zugehörige Order später komplett zurückerstattet wird.


<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">

    <!-- License pool: pre-generated keys for downloadable license products -->
    <table name="mironsoft_license_pool" resource="default" engine="innodb"
           comment="License key pool for downloadable products">
        <column xsi:type="int" name="entity_id" padding="10" unsigned="true"
                nullable="false" identity="true" comment="Entity ID"/>
        <column xsi:type="int" name="product_id" padding="10" unsigned="true"
                nullable="false" comment="Linked downloadable product ID"/>
        <column xsi:type="varchar" name="license_key" nullable="false" length="128"
                comment="Generated license key"/>
        <column xsi:type="int" name="order_item_id" padding="10" unsigned="true"
                nullable="true" comment="Assigned sales order item ID"/>
        <column xsi:type="smallint" name="status" padding="5" unsigned="true"
                nullable="false" default="0" comment="0 available, 1 assigned, 2 revoked"/>
        <column xsi:type="timestamp" name="assigned_at" nullable="true"
                comment="Timestamp of assignment"/>
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <column name="entity_id"/>
        </constraint>
        <constraint xsi:type="unique" referenceId="MIRONSOFT_LICENSE_POOL_LICENSE_KEY">
            <column name="license_key"/>
        </constraint>
        <constraint xsi:type="foreign" referenceId="MIRONSOFT_LICENSE_POOL_ORDER_ITEM_ID_SALES_ORDER_ITEM_ITEM_ID"
                    table="mironsoft_license_pool" column="order_item_id"
                    referenceTable="sales_order_item" referenceColumn="item_id" onDelete="SET NULL"/>
        <index referenceId="MIRONSOFT_LICENSE_POOL_PRODUCT_ID_STATUS" indexType="btree">
            <column name="product_id"/>
            <column name="status"/>
        </index>
    </table>
</schema>

8. E-Mail-Zustellung: Transaktionale Mails mit Download-Links

Nach Freischaltung eines Downloadable-Produkts versendet Magento automatisch eine transaktionale E-Mail mit den Download-Links, gesteuert über Magento\Downloadable\Model\Link\Purchased und den Template-Bereich unter Stores > Konfiguration > Kundenkonfiguration > Downloadable Product Options. Das Standardtemplate downloadable_email_links_template lässt sich über einen eigenen Layout-Handle anpassen, um zum Beispiel den zugewiesenen Lizenzschlüssel aus dem in Abschnitt 7 beschriebenen Pool zusätzlich in die Mail einzublenden.

Für den Fall, dass ein Kunde die ursprüngliche Mail verliert oder ein Downloadlink neu generiert werden muss, bietet der Admin-Bereich unter Sales > Orders > Downloadable Information die Möglichkeit, Links erneut zu versenden, ohne die Bestellung selbst zu verändern. Technisch triggert dieser Admin-Button denselben Mail-Versand-Service wie beim automatischen Versand nach der Rechnung, wodurch sich benutzerdefinierte Plugins, die etwa den Lizenzschlüssel injizieren, konsistent an beiden Stellen greifen, sofern sie am Service Contract statt am Controller ansetzen.


declare(strict_types=1);

namespace Mironsoft\DownloadGuard\Plugin;

use Magento\Downloadable\Model\Link\Purchased;
use Mironsoft\LicensePool\Api\LicenseKeyRepositoryInterface;

/**
 * Injects the assigned license key into the downloadable link email data
 * so customers receive their key alongside the download link.
 */
class AddLicenseKeyToEmailPlugin
{
    /**
     * @param LicenseKeyRepositoryInterface $licenseKeyRepository Repository for pooled license keys
     */
    public function __construct(
        private readonly LicenseKeyRepositoryInterface $licenseKeyRepository,
    ) {
    }

    /**
     * Adds the license key as an extra template variable before the mail is sent.
     *
     * @param Purchased $subject The purchased-link aggregate being emailed
     * @param array $result Template variables passed to the transactional email
     * @return array Modified template variables including the license key
     */
    public function afterGetEmailVariables(Purchased $subject, array $result): array
    {
        $orderItemId = (int) $subject->getData('order_item_id');
        $licenseKey = $this->licenseKeyRepository->getByOrderItemId($orderItemId);

        if ($licenseKey !== null) {
            $result['license_key'] = $licenseKey->getLicenseKey();
        }

        return $result;
    }
}

9. Downloadable-Produkte im Vergleich: Storage- und Sicherheitsoptionen

Bei der Konfiguration von Downloadable-Produkten stehen mehrere technische Entscheidungen zur Wahl, die sich direkt auf Sicherheit, Performance und Einsatzzweck auswirken. Die folgende Übersicht fasst die wichtigsten Optionen zusammen, wie sie in Magento 2 typischerweise für digitale Güter mit unterschiedlichem Schutzbedarf konfiguriert werden.

Option Sicherheit Performance Einsatzzweck
Link-Type "file" Hoch, Zugriff läuft über Controller Abhängig von Storage-Backend Lizenzpflichtige Software, sensible Dateien
Link-Type "url" Niedrig, falls extern ungeschützt Hoch, CDN-fähig Große Mediendateien mit eigenem Schutz
Sample statt Link Bewusst öffentlich Unkritisch Leseprobe, Vorschau ohne Kauf
Download-Limit unbegrenzt (0) Niedrig, kein Missbrauchsschutz Unkritisch Kulante Kundenerfahrung, geringes Risiko
Download-Limit begrenzt Hoch, technische Weitergabe-Bremse Unkritisch Software-Lizenzen, kommerzielle Inhalte
Signierte, zeitlich begrenzte URL Sehr hoch, verhindert Hotlinking Zusatzlogik pro Request Hochwertige, schutzbedürftige Downloads

10. Zusammenfassung

Downloadable-Produkte in Magento 2 lösen ein grundlegend anderes Problem als physische Produkte: Sobald eine Datei einmal ausgeliefert ist, entzieht sie sich der weiteren Kontrolle des Shops. Die technische Antwort darauf ist ein mehrschichtiges Modell: link_url_key sorgt dafür, dass der reale Dateipfad niemals sichtbar wird, Order-Status-Gating verhindert Zugriff vor abgeschlossener Zahlung, Download-Limits bremsen die unkontrollierte Weitergabe, und ein eigenes Lizenzschlüssel-Modul bindet den Kauf an eine konkrete, individuelle Berechtigung.

Wer diese Schichten sauber über Service Contracts, db_schema.xml und Repositories statt über direkte Model-Manipulation umsetzt, bekommt ein Setup, das sich mit Magento-Updates weiterentwickelt statt bei jedem Upgrade neu gepatcht werden zu müssen. Für Agenturen, die Software-Lizenzen oder hochwertige digitale Inhalte über Downloadable-Produkte vertreiben, ist diese Investition in Link-Sicherheit und Zugriffskontrolle kein optionales Extra, sondern Grundvoraussetzung für ein tragfähiges Lizenzgeschäft.

Downloadable-Produkte in Magento 2, das Wichtigste auf einen Blick

Datenmodell

downloadable_link, downloadable_sample und downloadable_link_purchased trennen Produktdefinition, Bestellkontext und Nutzungszähler sauber.

Link-Sicherheit

link_url_key als zufälliger Hash, niemals der reale Dateipfad sichtbar. link_type "file" statt "url" für sensible Downloads.

Zugriffskontrolle

Order-Status-Gating und number_of_downloads-Limits verhindern verfrühten und unbegrenzten Zugriff.

Lizenzpool & signierte URLs

Eigenes Modul mit db_schema.xml für Lizenzschlüssel, zusätzlich signierte, zeitlich begrenzte Download-URLs gegen Hotlinking.

11. FAQ: Downloadable-Produkte in Magento 2

1Downloadable-Produkt vs. Virtual Product?
Virtual Product bildet eine Leistung ohne Zustellung ab. Downloadable-Produkt liefert echte Dateien über eine eigene Infrastruktur mit Link-Hashing und Download-Limits aus.
2Welche Tabellen speichern Downloadable-Produkte?
downloadable_link und downloadable_sample für Produktdaten, downloadable_link_purchased und downloadable_link_purchased_item für Bestellkontext und Nutzungszähler.
3Wie schützt link_url_key?
Ein zufälliger Hash ersetzt im Frontend den realen Dateipfad. Der Controller löst ihn serverseitig auf, der Kunde sieht nie den tatsächlichen Speicherort.
4link_type file vs. url?
file speichert intern mit vollem Zugriffsschutz. url verweist extern und kann ungeschützt direkt aufrufbar sein, wenn keine eigene Absicherung existiert.
5Wann wird der Download freigeschaltet?
Erst bei erreichtem Order-Status, meist Pending oder Invoiced. Davor verweigert der Controller den Zugriff, auch mit korrektem Hash.
6Wie funktioniert das Download-Limit?
number_of_downloads legt das Maximum fest, 0 bedeutet unbegrenzt. Jeder Download erhöht number_of_downloads_used, geprüft vor jeder Auslieferung.
7Programmatisch anlegen?
ProductRepositoryInterface mit typeId downloadable, Links über LinkInterfaceFactory als Extension Attribute setzen, dann save() aufrufen.
8Wozu signierte URLs?
Verhindern dauerhafte Gültigkeit und Hotlinking. Nach Ablauf des Zeitfensters muss eine neue signierte URL über das Kundenkonto angefordert werden.
9Lizenzschlüssel-Pool aufbauen?
Eigenes Modul mit db_schema.xml und Observer auf sales_order_place_after, der Schlüssel transaktional reserviert und mit order_item_id verknüpft.
10Reicht die Standardkonfiguration?
Für einfache digitale Güter meist ja. Für Software-Lizenzen empfiehlt sich zusätzlich ein Lizenzpool, signierte URLs und ein striktes Download-Limit.

Mironsoft

Magento 2 Entwicklung, Hyva-Themes und E-Commerce-Sicherheit

Downloadable-Produkte sicher in Magento 2 einrichten?

Wir konzipieren und implementieren die passende Architektur für eure Downloadable-Produkte, von der Link-Sicherheit über Download-Limits bis zum eigenen Lizenzschlüssel-Pool, sauber über Service Contracts und db_schema.xml umgesetzt.

Sicherheits-Audit

Prüfung bestehender Downloadable-Produkte auf Link-Sicherheit und Zugriffskontrolle

Lizenzpool-Modul

Eigenes Modul für Lizenzschlüssel-Verwaltung und automatische Zuweisung

Signierte Auslieferung

Custom Controller mit zeitlich begrenzten, signierten Download-URLs