diff --git a/core/lib/Drupal/Core/Access/AccessCheckInterface.php b/core/lib/Drupal/Core/Access/AccessCheckInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0280330c19627751d3596d1bfc904bfc829fd929
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/AccessCheckInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Access\AccessCheckInterface.
+ */
+
+namespace Drupal\Core\Access;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * An access check service determines access rules for particular routes.
+ */
+interface AccessCheckInterface {
+
+  /**
+   * Declares whether the access check applies to a specific route or not.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to consider attaching to.
+   *
+   * @return bool
+   *   TRUE if the check applies to the passed route, FALSE otherwise.
+   */
+  public function applies(Route $route);
+
+  /**
+   * Checks for access to route.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check against.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   *
+   * @return mixed
+   *   TRUE if access is allowed.
+   *   FALSE if not.
+   *   NULL if no opinion.
+   */
+  public function access(Route $route, Request $request);
+}
diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..33d3e9dcd12cdd86b2de828f95045ed7285c5b9f
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/AccessManager.php
@@ -0,0 +1,150 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Core\Access\AccessManager.
+ */
+
+namespace Drupal\Core\Access;
+
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\DependencyInjection\ContainerAware;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * Attaches access check services to routes and runs them on request.
+ */
+class AccessManager extends ContainerAware {
+
+  /**
+   * Array of registered access check service ids.
+   *
+   * @var array
+   */
+  protected $checkIds;
+
+  /**
+   * Array of access check objects keyed by service id.
+   *
+   * @var array
+   */
+  protected $checks;
+
+  /**
+   * The request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * Constructs a new AccessManager.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   */
+  public function __construct(Request $request) {
+    $this->request = $request;
+  }
+
+  /**
+   * Registers a new AccessCheck by service ID.
+   *
+   * @param string $service_id
+   *   The ID of the service in the Container that provides a check.
+   */
+  public function addCheckService($service_id) {
+    $this->checkIds[] = $service_id;
+  }
+
+  /**
+   * For each route, saves a list of applicable access checks to the route.
+   *
+   * @param \Symfony\Component\Routing\RouteCollection $routes
+   *   A collection of routes to apply checks to.
+   */
+  public function setChecks(RouteCollection $routes) {
+    foreach ($routes as $route) {
+      $checks = $this->applies($route);
+      if (!empty($checks)) {
+        $route->setOption('_access_checks', $checks);
+      }
+    }
+  }
+
+  /**
+   * Determine which registered access checks apply to a route.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to get list of access checks for.
+   *
+   * @return array
+   *   An array of service ids for the access checks that apply to passed
+   *   route.
+   */
+  protected function applies(Route $route) {
+    $checks = array();
+
+    foreach ($this->checkIds as $service_id) {
+      if (empty($this->checks[$service_id])) {
+        $this->loadCheck($service_id);
+      }
+
+      if ($this->checks[$service_id]->applies($route)) {
+        $checks[] = $service_id;
+      }
+    }
+
+    return $checks;
+  }
+
+  /**
+   * Checks a route against applicable access check services.
+   *
+   * Determines whether the route is accessible or not.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check access to.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   If any access check denies access or none explicitly approve.
+   */
+  public function check(Route $route) {
+    $access = FALSE;
+    $checks = $route->getOption('_access_checks') ?: array();
+
+    // No checks == deny by default.
+    foreach ($checks as $service_id) {
+      if (empty($this->checks[$service_id])) {
+        $this->loadCheck($service_id);
+      }
+
+      $access = $this->checks[$service_id]->access($route, $this->request);
+      if ($access === FALSE) {
+        // A check has denied access, no need to continue checking.
+        break;
+      }
+    }
+
+    // Access has been denied or not explicily approved.
+    if (!$access) {
+      throw new AccessDeniedHttpException();
+    }
+  }
+
+  /**
+   * Lazy-loads access check services.
+   *
+   * @param string $service_id
+   *   The service id of the access check service to load.
+   */
+  protected function loadCheck($service_id) {
+    if (!in_array($service_id, $this->checkIds)) {
+      throw new \InvalidArgumentException(sprintf('No check has been registered for %s', $service_id));
+    }
+
+    $this->checks[$service_id] = $this->container->get($service_id);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..80afdef2ce4c7c25f00b7221eec26da5d7f978c4
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Access\DefaultAccessCheck.
+ */
+
+namespace Drupal\Core\Access;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Allows access to routes to be controlled by an '_access' boolean parameter.
+ */
+class DefaultAccessCheck implements AccessCheckInterface {
+
+  /**
+   * Implements AccessCheckInterface::applies().
+   */
+  public function applies(Route $route) {
+    return array_key_exists('_access', $route->getRequirements());
+  }
+
+  /**
+   * Implements AccessCheckInterface::access().
+   */
+  public function access(Route $route, Request $request) {
+    return $route->getRequirement('_access');
+  }
+}
diff --git a/core/lib/Drupal/Core/Access/PermissionAccessCheck.php b/core/lib/Drupal/Core/Access/PermissionAccessCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..ee3768b157314cffa677709fd4b7eb6f2c8b0618
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/PermissionAccessCheck.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Access\PermissionAccessCheck.
+ */
+
+namespace Drupal\Core\Access;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Determines access to routes based on permissions defined via hook_permission().
+ */
+class PermissionAccessCheck implements AccessCheckInterface {
+
+  /**
+   * Implements AccessCheckInterface::applies().
+   */
+  public function applies(Route $route) {
+    return array_key_exists('_permission', $route->getRequirements());
+  }
+
+  /**
+   * Implements AccessCheckInterface::access().
+   */
+  public function access(Route $route, Request $request) {
+    $permission = $route->getRequirement('_permission');
+    // @todo Replace user_access() with a correctly injected and session-using
+    //   alternative.
+    // If user_access() fails, return NULL to give other checks a chance.
+    return user_access($permission) ? TRUE : NULL;
+  }
+}
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 71ef37b454e0a929d394dfd7fd0956e928870b7f..8b4eaaa70b92c0352aaed156fc78a4af73d26f33 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core;
 
 use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterSerializationClassesPass;
@@ -179,8 +180,18 @@ public function build(ContainerBuilder $container) {
     $container->register('view_subscriber', 'Drupal\Core\EventSubscriber\ViewSubscriber')
       ->addArgument(new Reference('content_negotiation'))
       ->addTag('event_subscriber');
+    $container->register('legacy_access_subscriber', 'Drupal\Core\EventSubscriber\LegacyAccessSubscriber')
+      ->addTag('event_subscriber');
+    $container->register('access_manager', 'Drupal\Core\Access\AccessManager')
+      ->addArgument(new Reference('request'))
+      ->addMethodCall('setContainer', array(new Reference('service_container')));
     $container->register('access_subscriber', 'Drupal\Core\EventSubscriber\AccessSubscriber')
+      ->addArgument(new Reference('access_manager'))
       ->addTag('event_subscriber');
+    $container->register('access_check.default', 'Drupal\Core\Access\DefaultAccessCheck')
+      ->addTag('access_check');
+    $container->register('access_check.permission', 'Drupal\Core\Access\PermissionAccessCheck')
+      ->addTag('access_check');
     $container->register('maintenance_mode_subscriber', 'Drupal\Core\EventSubscriber\MaintenanceModeSubscriber')
       ->addTag('event_subscriber');
     $container->register('path_subscriber', 'Drupal\Core\EventSubscriber\PathSubscriber')
@@ -221,6 +232,9 @@ public function build(ContainerBuilder $container) {
     $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
     // Add a compiler pass for adding Normalizers and Encoders to Serializer.
     $container->addCompilerPass(new RegisterSerializationClassesPass());
+    // Add a compiler pass for registering event subscribers.
+    $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
+    $container->addCompilerPass(new RegisterAccessChecksPass());
   }
 
 }
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php
new file mode 100644
index 0000000000000000000000000000000000000000..9e6506d3f285d6abd3a3b968b947ceb28a47cb07
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Adds services tagged 'access_check' to the access_manager service.
+ */
+class RegisterAccessChecksPass implements CompilerPassInterface {
+
+  /**
+   * Implements CompilerPassInterface::process().
+   *
+   * Adds services tagged 'access_check' to the access_manager service.
+   */
+  public function process(ContainerBuilder $container) {
+    if (!$container->hasDefinition('access_manager')) {
+      return;
+    }
+    $access_manager = $container->getDefinition('access_manager');
+    foreach ($container->findTaggedServiceIds('access_check') as $id => $attributes) {
+      $access_manager->addMethodCall('addCheckService', array($id));
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
index 4f1dc7545dff73f9754150e5b3a4239d7c5f1c02..4cfd0b6763ff6697cd458cbcafecdb504a65a978 100644
--- a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
@@ -2,15 +2,17 @@
 
 /**
  * @file
- * Definition of Drupal\Core\EventSubscriber\AccessSubscriber.
+ * Contains Drupal\Core\EventSubscriber\AccessSubscriber.
  */
 
 namespace Drupal\Core\EventSubscriber;
 
 use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Drupal\Core\Routing\RoutingEvents;
+use Drupal\Core\Access\AccessManager;
+use Drupal\Core\Routing\RouteBuildEvent;
 
 /**
  * Access subscriber for controller requests.
@@ -18,21 +20,41 @@
 class AccessSubscriber implements EventSubscriberInterface {
 
   /**
-   * Verifies that the current user can access the requested path.
+   * Constructs a new AccessSubscriber.
    *
-   * @todo This is a total hack to keep our current access system working. It
-   *   should be replaced with something robust and injected at some point.
+   * @param \Drupal\Core\Access\AccessManager $access_manager
+   *   The access check manager that will be responsible for applying
+   *   AccessCheckers against routes.
+   */
+  public function __construct(AccessManager $access_manager) {
+    $this->accessManager = $access_manager;
+  }
+
+  /**
+   * Verifies that the current user can access the requested path.
    *
-   * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
    *   The Event to process.
    */
   public function onKernelRequestAccessCheck(GetResponseEvent $event) {
+    $request = $event->getRequest();
+    if (!$request->attributes->has('_route')) {
+      // If no Route is available it is likely a static resource and access is
+      // handled elsewhere.
+      return;
+    }
 
-    $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+    $this->accessManager->check($request->attributes->get('_route'));
+  }
 
-    if (isset($router_item['access']) && !$router_item['access']) {
-      throw new AccessDeniedHttpException();
-    }
+  /**
+   * Apply access checks to routes.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The event to process.
+   */
+  public function onRoutingRouteAlterSetAccessCheck(RouteBuildEvent $event) {
+    $this->accessManager->setChecks($event->getRouteCollection());
   }
 
   /**
@@ -43,6 +65,8 @@ public function onKernelRequestAccessCheck(GetResponseEvent $event) {
    */
   static function getSubscribedEvents() {
     $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30);
+    // Setting very low priority to ensure access checks are run after alters.
+    $events[RoutingEvents::ALTER][] = array('onRoutingRouteAlterSetAccessCheck', 0);
 
     return $events;
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..566e82ce23702078d3a1c641cb1bc50a9829b6af
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\EventSubscriber\LegacyAccessSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Access subscriber for legacy controller requests.
+ */
+class LegacyAccessSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Verifies that the current user can access the requested path.
+   *
+   * @todo This is a total hack to keep our current access system working. It
+   *   should be replaced with something robust and injected at some point.
+   *
+   * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelRequestAccessCheck(GetResponseEvent $event) {
+
+    $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+
+    if (isset($router_item['access']) && !$router_item['access']) {
+      throw new AccessDeniedHttpException();
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php
index 45d0888c51744baa0534c57879c965d05794f77c..cc1adde62a9274998e7bcc4891f8a1980a6f3f72 100644
--- a/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php
+++ b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php
@@ -53,7 +53,8 @@ public function matchRequest(Request $request) {
 
       preg_match($compiled->getRegex(), $path, $matches);
 
-      return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $name));
+      $route->setOption('_name', $name);
+      return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $route));
     }
   }
 
diff --git a/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php b/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php
index f76d535489c89997da4b9600879f98007aa13feb..83fdd63b0393f53d8b2fc4e93a46ee23fe587710 100644
--- a/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php
+++ b/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php
@@ -62,6 +62,7 @@ public function dynamicRoutes(RouteBuildEvent $event) {
 
         // @todo Switch to ->addCollection() once http://drupal.org/node/1819018 is resolved.
         foreach ($plugin->routes() as $name => $route) {
+          $route->setRequirement('_access', 'TRUE');
           $collection->add("rest.$name", $route);
         }
       }
diff --git a/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php b/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..117dc1a07f5a8918d94ae0808d18aa3cd0e42fac
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Access\CronAccessCheck.
+ */
+
+namespace Drupal\system\Access;
+
+use Drupal\Core\Access\AccessCheckInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Access check for cron routes.
+ */
+class CronAccessCheck implements AccessCheckInterface {
+
+  /**
+   * Implements AccessCheckInterface::applies().
+   */
+  public function applies(Route $route) {
+    return array_key_exists('_access_system_cron', $route->getRequirements());
+  }
+
+  /**
+   * Implements AccessCheckInterface::access().
+   */
+  public function access(Route $route, Request $request) {
+    $key = $request->attributes->get('key');
+    if ($key != state()->get('system.cron_key')) {
+      watchdog('cron', 'Cron could not run because an invalid key was used.', array(), WATCHDOG_NOTICE);
+      return FALSE;
+    }
+    elseif (config('system.maintenance')->get('enabled')) {
+      watchdog('cron', 'Cron could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE);
+      return FALSE;
+    }
+    return TRUE;
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/CronController.php b/core/modules/system/lib/Drupal/system/CronController.php
index 292d242f4036827ee9a74e4f03a7b573e2b56c6d..1a7e50e9384b38554ec50c083fee0e8f141efdb5 100644
--- a/core/modules/system/lib/Drupal/system/CronController.php
+++ b/core/modules/system/lib/Drupal/system/CronController.php
@@ -8,10 +8,9 @@
 namespace Drupal\system;
 
 use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 
 /**
- * Controllers for Cron handling.
+ * Controller for Cron handling.
  */
 class CronController {
 
@@ -21,35 +20,11 @@ class CronController {
    * @return Symfony\Component\HttpFoundation\Response
    *   A Symfony response object.
    */
-  public function run($key) {
-    if (!$this->access($key)) {
-      throw new AccessDeniedHttpException();
-    }
-
+  public function run() {
     // @todo Make this an injected object.
     drupal_cron_run();
 
     // HTTP 204 is "No content", meaning "I did what you asked and we're done."
     return new Response('', 204);
   }
-
-  /**
-   * Determines if the user has access to run cron.
-   *
-   * @todo Eliminate this method in favor of a new-style access checker once
-   * http://drupal.org/node/1793520 gets in.
-   */
-  function access($key) {
-    if ($key != state()->get('system.cron_key')) {
-      watchdog('cron', 'Cron could not run because an invalid key was used.', array(), WATCHDOG_NOTICE);
-      return FALSE;
-    }
-    elseif (config('system.maintenance')->get('enabled')) {
-      watchdog('cron', 'Cron could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE);
-      return FALSE;
-    }
-
-    return TRUE;
-  }
-
 }
diff --git a/core/modules/system/lib/Drupal/system/SystemBundle.php b/core/modules/system/lib/Drupal/system/SystemBundle.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ea60179f9db7165e682d9f5ca5ebb5a4a7fb2ff
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/SystemBundle.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\SystemBundle.
+ */
+
+namespace Drupal\system;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * System dependency injection container.
+ */
+class SystemBundle extends Bundle {
+
+  /**
+   * Overrides Bundle::build().
+   */
+  public function build(ContainerBuilder $container) {
+    $container->register('access_check.cron', 'Drupal\system\Access\CronAccessCheck')
+      ->addTag('access_check');
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php
index a288b9eb16e0af913162f62d7ecd470ab03bd453..c44a49247040fac60a7d43ed9dcc9f034b43041a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php
@@ -61,7 +61,7 @@ public function testFinalMatcherStatic() {
     $matcher->setCollection($collection);
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
     $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.');
   }
 
@@ -82,7 +82,7 @@ public function testFinalMatcherPattern() {
     $matcher->setCollection($collection);
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
     $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.');
     $this->assertEqual($attributes['value'], 'narf', 'Required placeholder value found.');
   }
@@ -105,7 +105,7 @@ public function testFinalMatcherPatternDefalts() {
     $matcher->setCollection($collection);
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
     $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.');
     $this->assertEqual($attributes['value'], 'poink', 'Optional placeholder value used default.');
   }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php
index c98da2eef301bd00b0782e07d1d18ee7e85042a8..8055743ee0940b1d90ee1848ba760a029fd763c6 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php
@@ -44,7 +44,7 @@ function __construct($test_id = NULL) {
 
     $this->fixtures = new RoutingFixtures();
   }
-  
+
   /**
    * Confirms that the HttpMethod matcher matches properly.
    */
@@ -78,7 +78,7 @@ public function testNestedMatcher() {
 
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php
index 850cc7e96ecd105cf15b0d2753e4a9d4228d00b0..8c9a1d8f1dca67a5aa3a3b731fc25553e3470cf7 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php
@@ -93,7 +93,7 @@ public function testNestedMatcher() {
     $request->headers->set('Accept', 'text/html, text/xml;q=0.9');
 
     $attributes = $matcher->matchRequest($request);
-    $this->assertEqual($attributes['_route'], 'route_e', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_e', 'The correct matching route was found.');
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php
index 444785c49f07b98c99905ab8dde4198b6aca1896..de295389f519ba605fcafb44dcd788cc55fd6ab0 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php
@@ -60,6 +60,6 @@ public function testNestedMatcher() {
 
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
   }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6980886ed16967d19767d350d0bb11d5f3d4a1fa
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\Routing\RouterPermissionTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Basic tests for access permissions in routes.
+ */
+class RouterPermissionTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('router_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Router Permission tests',
+      'description' => 'Function Tests for the routing permission system.',
+      'group' => 'Routing',
+    );
+  }
+
+  /**
+   * Tests permission requirements on routes.
+   */
+  public function testPermissionAccess() {
+
+    $this->drupalGet('router_test/test7');
+    $this->assertResponse(403, "Access denied for a route where we don't have a permission");
+
+    $this->drupalGet('router_test/test8');
+    $this->assertResponse(403, 'Access denied by default if no access specified');
+
+    $user = $this->drupalCreateUser(array('access test7'));
+    $this->drupalLogin($user);
+    $this->drupalGet('router_test/test7');
+    $this->assertResponse(200);
+    $this->assertNoRaw('Access denied');
+    $this->assertRaw('test7text', 'The correct string was returned because the route was successful.');
+  }
+}
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 85e051763cce737a30df2e3003652f29d1706cd4..085895e2420821dee022bfb4c516ca5a957fd77f 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -1,4 +1,6 @@
-cron:
+system.cron:
   pattern: '/cron/{key}'
   defaults:
     _controller: '\Drupal\system\CronController::run'
+  requirements:
+    _access_system_cron: 'TRUE'
diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php
index a67e83f2702d43b9cdf3ef3e9808adac4419ed9e..4243f81beafa0c89488874e493f633bdb1672d5c 100644
--- a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php
+++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php
@@ -35,6 +35,8 @@ public function dynamicRoutes(RouteBuildEvent $event) {
     $collection = $event->getRouteCollection();
     $route = new Route('/router_test/test5', array(
       '_content' => '\Drupal\router_test\TestControllers::test5'
+    ), array(
+      '_access' => 'TRUE'
     ));
     $collection->add('router_test_5', $route);
   }
diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php
index bcf18b7b0fdd18985562abc444c1c772eff69fe4..adb5c345e0f184e715484e47d8156184a231c3c0 100644
--- a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php
+++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php
@@ -34,4 +34,16 @@ public function test5() {
     return "test5";
   }
 
+  public function test6() {
+    return new Response('test6');
+  }
+
+  public function test7() {
+    return new Response('test7text');
+  }
+
+  public function test8() {
+    return new Response('test8');
+  }
+
 }
diff --git a/core/modules/system/tests/modules/router_test/router_test.module b/core/modules/system/tests/modules/router_test/router_test.module
index b3d9bbc7f3711e882119cd6b3af051245d859d04..ffbe070b8ea78bd52fd4ebae0df024c1479447dd 100644
--- a/core/modules/system/tests/modules/router_test/router_test.module
+++ b/core/modules/system/tests/modules/router_test/router_test.module
@@ -1 +1,13 @@
 <?php
+
+/**
+ * Implements hook_permission().
+ */
+function router_test_permission() {
+  return array(
+    'access test7' =>  array(
+      'title' => t('Access test7 route'),
+      'description' => t('Test permission only.'),
+    ),
+  );
+}
diff --git a/core/modules/system/tests/modules/router_test/router_test.routing.yml b/core/modules/system/tests/modules/router_test/router_test.routing.yml
index cc177d38b37b2681068ffa59407a45de124c0c19..2c94ff245cf9ec74b1ce408dbb8df2d4af9e1a65 100644
--- a/core/modules/system/tests/modules/router_test/router_test.routing.yml
+++ b/core/modules/system/tests/modules/router_test/router_test.routing.yml
@@ -2,24 +2,46 @@ router_test_1:
   pattern: '/router_test/test1'
   defaults:
     _controller: '\Drupal\router_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
 
 router_test_2:
   pattern: '/router_test/test2'
   defaults:
     _content: '\Drupal\router_test\TestControllers::test2'
+  requirements:
+    _access: 'TRUE'
 
 router_test_3:
   pattern: '/router_test/test3/{value}'
   defaults:
     _content: '\Drupal\router_test\TestControllers::test3'
+  requirements:
+    _access: 'TRUE'
 
 router_test_4:
   pattern: '/router_test/test4/{value}'
   defaults:
     _content: '\Drupal\router_test\TestControllers::test4'
     value: 'narf'
+  requirements:
+    _access: 'TRUE'
 
 router_test_6:
   pattern: '/router_test/test6'
   defaults:
     _controller: '\Drupal\router_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
+
+router_test_7:
+  pattern: '/router_test/test7'
+  defaults:
+    _controller: '\Drupal\router_test\TestControllers::test7'
+  requirements:
+    _permission: 'access test7'
+
+router_test_8:
+  pattern: '/router_test/test8'
+  defaults:
+    _controller: '\Drupal\router_test\TestControllers::test8'
diff --git a/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php b/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..f8adae4e7fa8082e7ce2d468358b57ce1e464928
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\user\Access\RegisterAccessCheck.
+ */
+
+namespace Drupal\user\Access;
+
+use Drupal\Core\Access\AccessCheckInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Access check for user registration routes.
+ */
+class RegisterAccessCheck implements AccessCheckInterface {
+
+  /**
+   * Implements AccessCheckInterface::applies().
+   */
+  public function applies(Route $route) {
+    return array_key_exists('_access_user_register', $route->getRequirements());
+  }
+
+  /**
+   * Implements AccessCheckInterface::access().
+   */
+  public function access(Route $route, Request $request) {
+    return user_is_anonymous() && (config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY);
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/UserBundle.php b/core/modules/user/lib/Drupal/user/UserBundle.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ae8936a1dd71b324c2ee5c838698dfb5e3860b2
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/UserBundle.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\UserBundle.
+ */
+
+namespace Drupal\user;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * User dependency injection container.
+ */
+class UserBundle extends Bundle {
+
+  /**
+   * Overrides Bundle::build().
+   */
+  public function build(ContainerBuilder $container) {
+    $container->register('access_check.user.register', 'Drupal\user\Access\RegisterAccessCheck')
+      ->addTag('access_check');
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/UserRouteController.php b/core/modules/user/lib/Drupal/user/UserRouteController.php
index 0cb5a1507cb04f57ced284c80cce9d54b7ba05fa..cf3b50ea2e784c1ccaec717d7ff9281811568d9b 100644
--- a/core/modules/user/lib/Drupal/user/UserRouteController.php
+++ b/core/modules/user/lib/Drupal/user/UserRouteController.php
@@ -8,7 +8,6 @@
 namespace Drupal\user;
 
 use Symfony\Component\DependencyInjection\ContainerAware;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 
 /**
  * Returns responses for User module routes.
@@ -22,12 +21,6 @@ class UserRouteController extends ContainerAware {
    *   A renderable array containing the user registration form.
    */
   public function register() {
-    // @todo Remove once access control is integrated with new routing system:
-    //   http://drupal.org/node/1793520.
-    if (!user_register_access()) {
-      throw new AccessDeniedHttpException();
-    }
-
     $account = entity_create('user', array());
     return entity_get_form($account, 'register');
   }
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index 5e11fab24ef0f0492258ed06ce4d4711c0673a9f..c3a05d74f2805ebc297980a425373ab2c365e3f1 100644
--- a/core/modules/user/user.routing.yml
+++ b/core/modules/user/user.routing.yml
@@ -2,3 +2,5 @@ user_register:
   pattern: '/user/register'
   defaults:
     _content: '\Drupal\user\UserRouteController::register'
+  requirements:
+    _access_user_register: 'TRUE'