diff --git a/typo3/sysext/fluid/Classes/Core/Widget/AjaxWidgetContextHolder.php b/typo3/sysext/fluid/Classes/Core/Widget/AjaxWidgetContextHolder.php
index 24edbcbdd36c403c165775e8d95a6ed0f9a74ab4..3476e580e4996b6c144ffcb6005dbcaed225fb13 100644
--- a/typo3/sysext/fluid/Classes/Core/Widget/AjaxWidgetContextHolder.php
+++ b/typo3/sysext/fluid/Classes/Core/Widget/AjaxWidgetContextHolder.php
@@ -16,6 +16,8 @@
 namespace TYPO3\CMS\Fluid\Core\Widget;
 
 use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Security\Cryptography\HashService;
 use TYPO3\CMS\Fluid\Core\Widget\Exception\WidgetContextNotFoundException;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
@@ -41,11 +43,17 @@ class AjaxWidgetContextHolder implements SingletonInterface
      */
     protected $widgetContextsStorageKey = 'TYPO3\\CMS\\Fluid\\Core\\Widget\\AjaxWidgetContextHolder_widgetContexts';
 
+    /**
+     * @var HashService
+     */
+    protected $hashService;
+
     /**
      * Constructor
      */
     public function __construct()
     {
+        $this->hashService = GeneralUtility::makeInstance(HashService::class);
         $this->loadWidgetContexts();
     }
 
@@ -55,13 +63,9 @@ class AjaxWidgetContextHolder implements SingletonInterface
     protected function loadWidgetContexts()
     {
         if (isset($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) {
-            $this->widgetContexts = $this->buildWidgetContextsFromArray(
-                json_decode($GLOBALS['TSFE']->fe_user->getKey('ses', $this->widgetContextsStorageKey ?? null), true) ?? []
-            );
+            $this->widgetContexts = $this->unserializeWithHmac($GLOBALS['TSFE']->fe_user->getKey('ses', $this->widgetContextsStorageKey));
         } else {
-            $this->widgetContexts = $this->buildWidgetContextsFromArray(
-                json_decode($GLOBALS['BE_USER']->uc[$this->widgetContextsStorageKey] ?? '', true) ?? []
-            );
+            $this->widgetContexts = isset($GLOBALS['BE_USER']->uc[$this->widgetContextsStorageKey]) ? $this->unserializeWithHmac($GLOBALS['BE_USER']->uc[$this->widgetContextsStorageKey]) : [];
             $GLOBALS['BE_USER']->writeUC();
         }
     }
@@ -100,27 +104,33 @@ class AjaxWidgetContextHolder implements SingletonInterface
     protected function storeWidgetContexts()
     {
         if (isset($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) {
-            $GLOBALS['TSFE']->fe_user->setKey('ses', $this->widgetContextsStorageKey, json_encode($this->widgetContexts));
+            $GLOBALS['TSFE']->fe_user->setKey('ses', $this->widgetContextsStorageKey, $this->serializeWithHmac($this->widgetContexts));
             $GLOBALS['TSFE']->fe_user->storeSessionData();
         } else {
-            $GLOBALS['BE_USER']->uc[$this->widgetContextsStorageKey] = json_encode($this->widgetContexts);
+            $GLOBALS['BE_USER']->uc[$this->widgetContextsStorageKey] = $this->serializeWithHmac($this->widgetContexts);
             $GLOBALS['BE_USER']->writeUC();
         }
     }
 
-    /**
-     * Builds WidgetContext instances from JSON representation,
-     * this is basically required for AJAX widgets only.
-     *
-     * @param array $data
-     * @return WidgetContext[]
-     */
-    protected function buildWidgetContextsFromArray(array $data): array
+    protected function serializeWithHmac(array $widgetContexts): string
     {
-        $widgetContexts = [];
-        foreach ($data as $widgetId => $widgetContextData) {
-            $widgetContexts[$widgetId] = WidgetContext::fromArray($widgetContextData);
+        $data = serialize($widgetContexts);
+        return $this->hashService->appendHmac($data);
+    }
+
+    protected function unserializeWithHmac(?string $data): array
+    {
+        if ($data === null || $data === '') {
+            return [];
+        }
+        try {
+            $data = $this->hashService->validateAndStripHmac($data);
+            // widget contexts literally can contain everything, that why using
+            // HMAC-signed `unserialize()` is the only option here unless Extbase
+            // structures have been refactored further
+            $widgetContexts = unserialize($data);
+        } finally {
+            return is_array($widgetContexts ?? null) ? $widgetContexts : [];
         }
-        return $widgetContexts;
     }
 }
diff --git a/typo3/sysext/fluid/Classes/Core/Widget/WidgetContext.php b/typo3/sysext/fluid/Classes/Core/Widget/WidgetContext.php
index 5f2035182ade580ef807b7b9665db5f70dfdb836..0049f86a4a5818532f4f7e9dc7a3db3f1efc6c7b 100644
--- a/typo3/sysext/fluid/Classes/Core/Widget/WidgetContext.php
+++ b/typo3/sysext/fluid/Classes/Core/Widget/WidgetContext.php
@@ -29,19 +29,8 @@ use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
  *
  * @internal It is a purely internal class which should not be used outside of Fluid.
  */
-class WidgetContext implements \JsonSerializable
+class WidgetContext
 {
-    protected const JSON_SERIALIZABLE_PROPERTIES = [
-        'widgetIdentifier',
-        'ajaxWidgetIdentifier',
-        'widgetConfiguration',
-        'controllerObjectName',
-        'parentPluginNamespace',
-        'parentExtensionName',
-        'parentPluginName',
-        'widgetViewHelperClassName',
-    ];
-
     /**
      * Uniquely identifies a Widget Instance on a certain page.
      *
@@ -58,8 +47,7 @@ class WidgetContext implements \JsonSerializable
 
     /**
      * User-supplied widget configuration, available inside the widget
-     * controller as $this->widgetConfiguration. Array items must be
-     * null, scalar, array or implement \JsonSerializable interface.
+     * controller as $this->widgetConfiguration.
      *
      * @var array
      */
@@ -109,51 +97,6 @@ class WidgetContext implements \JsonSerializable
      */
     protected $widgetViewHelperClassName;
 
-    public static function fromArray(array $data): WidgetContext
-    {
-        $target = new WidgetContext();
-        foreach ($data as $propertyName => $propertyValue) {
-            if (!in_array($propertyName, self::JSON_SERIALIZABLE_PROPERTIES, true)) {
-                continue;
-            }
-            if (!property_exists($target, $propertyName)) {
-                continue;
-            }
-            $target->{$propertyName} = $propertyValue;
-        }
-        return $target;
-    }
-
-    public function jsonSerialize()
-    {
-        $properties = array_fill_keys(self::JSON_SERIALIZABLE_PROPERTIES, true);
-        $data = array_intersect_key(get_object_vars($this), $properties);
-
-        if (is_array($data['widgetConfiguration'] ?? null)) {
-            $data['widgetConfiguration'] = array_filter(
-                $data['widgetConfiguration'],
-                function ($value): bool {
-                    if ($value === null || is_scalar($value) || is_array($value)
-                        || is_object($value) && $value instanceof \JsonSerializable
-                    ) {
-                        return true;
-                    }
-                    throw new Exception(
-                        sprintf(
-                            'Widget configuration items either must be null, scalar, array or JSON serializable, got "%s"',
-                            is_object($value) ? get_class($value) : gettype($value)
-                        ),
-                        1588178538
-                    );
-                }
-            );
-        } else {
-            $data['widgetConfiguration'] = null;
-        }
-
-        return $data;
-    }
-
     /**
      * @return string
      */
@@ -323,4 +266,12 @@ class WidgetContext implements \JsonSerializable
     {
         return $this->viewHelperChildNodeRenderingContext;
     }
+
+    /**
+     * @return array
+     */
+    public function __sleep()
+    {
+        return ['widgetIdentifier', 'ajaxWidgetIdentifier', 'widgetConfiguration', 'controllerObjectName', 'parentPluginNamespace', 'parentExtensionName', 'parentPluginName', 'widgetViewHelperClassName'];
+    }
 }
diff --git a/typo3/sysext/fluid/Tests/Unit/Core/Widget/WidgetContextTest.php b/typo3/sysext/fluid/Tests/Unit/Core/Widget/WidgetContextTest.php
index 14035d27ede4f19e83938152961b488d31648dd6..1adc67137ad61ed888ac53cc1a3073f49981d1ee 100644
--- a/typo3/sysext/fluid/Tests/Unit/Core/Widget/WidgetContextTest.php
+++ b/typo3/sysext/fluid/Tests/Unit/Core/Widget/WidgetContextTest.php
@@ -129,14 +129,14 @@ class WidgetContextTest extends UnitTestCase
     /**
      * @test
      */
-    public function jsonEncodeContainsExpectedPropertyNames()
+    public function sleepReturnsExpectedPropertyNames()
     {
         self::assertEquals(
             [
                 'widgetIdentifier', 'ajaxWidgetIdentifier', 'widgetConfiguration', 'controllerObjectName',
                 'parentPluginNamespace', 'parentExtensionName', 'parentPluginName', 'widgetViewHelperClassName'
             ],
-            array_keys(json_decode(json_encode($this->widgetContext), true))
+            $this->widgetContext->__sleep()
         );
     }
 }