diff --git a/core/core.services.yml b/core/core.services.yml
index 608c439..d6e74ab 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -411,6 +411,11 @@ services:
     class: Drupal\Core\Theme\ThemeAccessCheck
     tags:
       - { name: access_check }
+  access_check.custom:
+    class: Drupal\Core\Access\CustomAccessCheck
+    arguments: ['@controller_resolver']
+    tags:
+      - { name: access_check }
   maintenance_mode_subscriber:
     class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber
     tags:
diff --git a/core/lib/Drupal/Core/Access/AccessException.php b/core/lib/Drupal/Core/Access/AccessException.php
new file mode 100644
index 0000000..ac13536
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/AccessException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Access\AccessException.
+ */
+
+namespace Drupal\Core\Access;
+
+/**
+ * Exception thrown when an access callback does not return an allowed value.
+ */
+class AccessException extends \RuntimeException {
+
+}
diff --git a/core/lib/Drupal/Core/Access/AccessInterface.php b/core/lib/Drupal/Core/Access/AccessInterface.php
index f555ecb..f247bbc 100644
--- a/core/lib/Drupal/Core/Access/AccessInterface.php
+++ b/core/lib/Drupal/Core/Access/AccessInterface.php
@@ -21,7 +21,7 @@
    * A checker should return this value to indicate that it grants access to a
    * route.
    */
-  const ALLOW = TRUE;
+  const ALLOW = 'TRUE';
 
   /**
    * Deny access.
@@ -29,7 +29,7 @@
    * A checker should return this value to indicate it does not grant access to
    * a route.
    */
-  const DENY = NULL;
+  const DENY = 'NULL';
 
   /**
    * Block access.
@@ -38,7 +38,7 @@
    * block access to this route, regardless of any other access checkers. Most
    * checkers should prefer DENY.
    */
-  const KILL = FALSE;
+  const KILL = 'FALSE';
 
   /**
    * Checks for access to a route.
diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php
index 5959fd0..86025ac 100644
--- a/core/lib/Drupal/Core/Access/AccessManager.php
+++ b/core/lib/Drupal/Core/Access/AccessManager.php
@@ -259,6 +259,11 @@ protected function checkAll(array $checks, Route $route, Request $request) {
       }
 
       $service_access = $this->checks[$service_id]->access($route, $request);
+
+      if (!in_array($service_access, array(AccessInterface::ALLOW, AccessInterface::DENY, AccessInterface::KILL))) {
+        throw new AccessException('Access services can only return AccessInterface::ALLOW, AccessInterface::DENY, or AccessInterface::KILL constants.');
+      }
+
       if ($service_access === AccessInterface::ALLOW) {
         $access = TRUE;
       }
@@ -295,6 +300,11 @@ protected function checkAny(array $checks, $route, $request) {
       }
 
       $service_access = $this->checks[$service_id]->access($route, $request);
+
+      if (!in_array($service_access, array(AccessInterface::ALLOW, AccessInterface::DENY, AccessInterface::KILL))) {
+        throw new AccessException('Access services can only return AccessInterface::ALLOW, AccessInterface::DENY, or AccessInterface::KILL constants.');
+      }
+
       if ($service_access === AccessInterface::ALLOW) {
         $access = TRUE;
       }
diff --git a/core/lib/Drupal/Core/Access/CustomAccessCheck.php b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
new file mode 100644
index 0000000..20cd388
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Access\CustomAccessCheck.
+ */
+
+namespace Drupal\Core\Access;
+
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Defines a access checker that allows to specify a custom function for access.
+ */
+class CustomAccessCheck implements StaticAccessCheckInterface {
+
+  /**
+   * The controller resolver.
+   *
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
+   */
+  protected $controllerResolver;
+
+  /**
+   * Constructs a CustomAccessCheck instance.
+   *
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
+   *   The controller resolver.
+   */
+  public function __construct(ControllerResolverInterface $controller_resolver) {
+    $this->controllerResolver = $controller_resolver;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_custom_access');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    $access_controller = $route->getRequirement('_custom_access');
+
+    $controller = $this->controllerResolver->getControllerFromDefinition($access_controller);
+    $arguments = $this->controllerResolver->getArguments($request, $controller);
+
+    return call_user_func_array($controller, $arguments);
+  }
+
+}
diff --git a/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php b/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php
index 7404555..3a7eb3c 100644
--- a/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php
+++ b/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php
@@ -7,12 +7,15 @@
 
 namespace Drupal\toolbar\Routing;
 
+use Drupal\Core\Access\AccessInterface;
+use Drupal\Core\Controller\ControllerBase;
 use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Defines a controller for the toolbar module.
  */
-class ToolbarController {
+class ToolbarController extends ControllerBase {
 
   /**
    * Returns the rendered subtree of each top-level toolbar link.
@@ -27,4 +30,12 @@ public function subtreesJsonp() {
     return $response;
   }
 
+  /**
+   * Checks access for the subtree controller.
+   */
+  public function checkSubTreeAccess(Request $request) {
+    $hash = $request->get('hash');
+    return ($this->currentUser()->hasPermission('access toolbar') && ($hash == _toolbar_get_subtrees_hash())) ? AccessInterface::ALLOW : AccessInterface::DENY;
+  }
+
 }
diff --git a/core/modules/toolbar/toolbar.routing.yml b/core/modules/toolbar/toolbar.routing.yml
index 609d0c7..a4aabb1 100644
--- a/core/modules/toolbar/toolbar.routing.yml
+++ b/core/modules/toolbar/toolbar.routing.yml
@@ -3,4 +3,4 @@ toolbar.subtrees:
   defaults:
     _controller: '\Drupal\toolbar\Routing\ToolbarController::subtreesJsonp'
   requirements:
-    _access_toolbar_subtree: 'TRUE'
+    _custom_access: '\Drupal\toolbar\Routing\ToolbarController::checkSubTreeAccess'
diff --git a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
index d12f5c0..1e04679 100644
--- a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
@@ -461,6 +461,67 @@ public function testCheckNamedRouteWithNonExistingRoute() {
   }
 
   /**
+   * Tests that an access checker throws an exception for not allowed values.
+   *
+   * @dataProvider providerTestCheckException
+   *
+   * @expectedException \Drupal\Core\Access\AccessException
+   */
+  public function testCheckException($return_value) {
+    $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
+
+    // Setup a test route for each access configuration.
+    $requirements = array(
+      '_test_incorrect_value' => 'TRUE',
+    );
+    $options = array(
+      '_access_checks' => array(
+        'test_incorrect_value',
+      ),
+    );
+    $route = new Route('', array(), $requirements, $options);
+
+    $this->routeProvider->expects($this->any())
+      ->method('getRouteByName')
+      ->will($this->returnValue($route));
+
+    $this->setupAccessChecker();
+
+    $request = new Request();
+
+    // Register a service that will return an incorrect value.
+    $access_check = $this->getMock('Drupal\Core\Access\StaticAccessCheckInterface');
+    $access_check->expects($this->any())
+      ->method('appliesTo')
+      ->will($this->returnValue(array('_test_incorrect_value')));
+    $access_check->expects($this->any())
+      ->method('access')
+      ->will($this->returnValue($return_value));
+    $this->container->register('test_incorrect_value', $access_check);
+    $this->accessManager->addCheckService('test_incorrect_value');
+
+    $this->accessManager->checkNamedRoute('test_incorrect_value', array(), $request);
+  }
+
+  /**
+   * Data provider for testCheckException.
+   *
+   * @return array
+   */
+  public function providerTestCheckException() {
+    return array(
+      array(TRUE),
+      array(FALSE),
+      array(NULL),
+      array(array()),
+      array(array(1)),
+      array('string'),
+      array(0),
+      array(1),
+    );
+  }
+
+  /**
    * Converts AccessCheckInterface constants to a string.
    *
    * @param mixed $constant
diff --git a/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
new file mode 100644
index 0000000..5eb1305
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Access\CustomAccessCheckTest.
+ */
+
+namespace Drupal\Tests\Core\Access;
+
+use Drupal\Core\Access\AccessInterface;
+use Drupal\Core\Access\CustomAccessCheck;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Tests the custom access checker.
+ *
+ * @see \Drupal\Core\Access\CustomAccessCheck
+ */
+class CustomAccessCheckTest extends UnitTestCase {
+
+  /**
+   * The access checker to test.
+   *
+   * @var \Drupal\Core\Access\CustomAccessCheck
+   */
+  protected $accessChecker;
+
+  /**
+   * The mocked controller resolver.
+   *
+   * @var \Drupal\Core\Controller\ControllerResolverInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $controllerResolver;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Custom access check',
+      'description' => 'Tests the custom access checker.',
+      'group' => 'Access'
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->controllerResolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface');
+    $this->accessChecker = new CustomAccessCheck($this->controllerResolver);
+  }
+
+
+  /**
+   * Tests the appliesTo method.
+   */
+  public function testAppliesTo() {
+    $this->assertEquals($this->accessChecker->appliesTo(), array('_custom_access'));
+  }
+
+  /**
+   * Test the access method.
+   */
+  public function testAccess() {
+    $request = new Request(array());
+
+    $this->controllerResolver->expects($this->at(0))
+      ->method('getControllerFromDefinition')
+      ->with('\Drupal\Tests\Core\Access\TestController::accessDeny')
+      ->will($this->returnValue(array(new TestController(), 'accessDeny')));
+
+    $this->controllerResolver->expects($this->at(1))
+      ->method('getArguments')
+      ->will($this->returnValue(array()));
+
+    $this->controllerResolver->expects($this->at(2))
+      ->method('getControllerFromDefinition')
+      ->with('\Drupal\Tests\Core\Access\TestController::accessAllow')
+      ->will($this->returnValue(array(new TestController(), 'accessAllow')));
+
+    $this->controllerResolver->expects($this->at(3))
+      ->method('getArguments')
+      ->will($this->returnValue(array()));
+
+    $this->controllerResolver->expects($this->at(4))
+      ->method('getControllerFromDefinition')
+      ->with('\Drupal\Tests\Core\Access\TestController::accessParameter')
+      ->will($this->returnValue(array(new TestController(), 'accessParameter')));
+
+    $this->controllerResolver->expects($this->at(5))
+      ->method('getArguments')
+      ->will($this->returnValue(array('parameter' => 'TRUE')));
+
+    $route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessDeny'));
+    $this->assertNull($this->accessChecker->access($route, $request));
+
+    $route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessAllow'));
+    $this->assertTrue($this->accessChecker->access($route, $request));
+
+    $route = new Route('/test-route', array('parameter' => 'TRUE'), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessParameter'));
+    $this->assertTrue($this->accessChecker->access($route, $request));
+  }
+
+}
+
+class TestController {
+
+  public function accessAllow() {
+    return AccessInterface::ALLOW;
+  }
+
+  public function accessDeny() {
+    return AccessInterface::DENY;
+  }
+
+  public function accessParameter($parameter) {
+    if ($parameter == 'TRUE') {
+      return AccessInterface::ALLOW;
+    }
+    else {
+      return AccessInterface::DENY;
+    }
+  }
+
+}
