diff --git a/core/modules/overlay/lib/Drupal/overlay/Access/DismissMessageAccessCheck.php b/core/modules/overlay/lib/Drupal/overlay/Access/DismissMessageAccessCheck.php
new file mode 100644
index 0000000..fc25a52
--- /dev/null
+++ b/core/modules/overlay/lib/Drupal/overlay/Access/DismissMessageAccessCheck.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\overlay\Access\DismissMessageAccessCheck
+ */
+
+namespace Drupal\overlay\Access;
+
+use Drupal\Core\Access\AccessCheckInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides an access check for overlay user dismiss message routes.
+ */
+class DismissMessageAccessCheck implements AccessCheckInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Route $route) {
+    return array_key_exists('_access_overlay_dismiss_message', $route->getRequirements());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    global $user;
+    if (!user_access('access overlay')) {
+      return FALSE;
+    }
+    // It's unlikely, but possible that "access overlay" permission is granted
+    // to the anonymous role. In this case, we do not display the message to
+    // disable the overlay, so there is nothing to dismiss.
+    if (empty($user->uid)) {
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/overlay/lib/Drupal/overlay/Controller/OverlayController.php b/core/modules/overlay/lib/Drupal/overlay/Controller/OverlayController.php
new file mode 100644
index 0000000..19d9f6f
--- /dev/null
+++ b/core/modules/overlay/lib/Drupal/overlay/Controller/OverlayController.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\overlay\Controller\OverlayController.
+ */
+
+namespace Drupal\overlay\Controller;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * Controller routines for overlay routes.
+ */
+class OverlayController {
+
+  /**
+   * Dismisses the overlay accessibility message for this user.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse
+   *   Redirects to the user's edit page.
+   */
+  public function overlayMessage(Request $request) {
+    global $user;
+
+    // @todo Integrate CSRF link token directly into routing system: http://drupal.org/node/1798296.
+    $token = $request->attributes->get('token');
+    if (!isset($token) || !drupal_valid_token($token, 'overlay')) {
+      throw new AccessDeniedHttpException();
+    }
+    $request->attributes->get('user.data')->set('overlay', $user->uid, 'message_dismissed', 1);
+    drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.'));
+    // Destination is normally given. Go to the user profile as a fallback.
+    return new RedirectResponse(url('user/' . $user->uid . '/edit', array('absolute' => TRUE)));
+  }
+
+}
diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module
index 40a0c76..0a316e5 100644
--- a/core/modules/overlay/overlay.module
+++ b/core/modules/overlay/overlay.module
@@ -35,12 +35,6 @@ function overlay_menu() {
     'access arguments' => array('access overlay'),
     'type' => MENU_CALLBACK,
   );
-  $items['overlay/dismiss-message'] = array(
-    'title' => '',
-    'page callback' => 'overlay_user_dismiss_message',
-    'access callback' => 'overlay_user_dismiss_message_access',
-    'type' => MENU_CALLBACK,
-  );
   return $items;
 }
 
@@ -220,56 +214,6 @@ function overlay_page_alter(&$page) {
 }
 
 /**
- * Access callback: Determines access to dismiss the accessibility message.
- *
- * @return
- *   TRUE if the user has permission to dismiss the accessibility message or if
- *   the user is anonymous. FALSE if otherwise.
- *
- * @see overlay_user_dismiss_message()
- * @see overlay_menu()
- */
-function overlay_user_dismiss_message_access() {
-  global $user;
-  if (!user_access('access overlay')) {
-    return FALSE;
-  }
-  // It's unlikely, but possible that "access overlay" permission is granted to
-  // the anonymous role. In this case, we do not display the message to disable
-  // the overlay, so there is nothing to dismiss.
-  if (empty($user->uid)) {
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/**
- * Page callback: Dismisses the overlay accessibility message for this user.
- *
- * @return
- *   A render array for a page containing a list of content.
- *
- * @see overlay_user_dismiss_message_access()
- * @see overlay_menu()
- */
-function overlay_user_dismiss_message() {
-  global $user;
-
-  // @todo CSRF tokens are validated in page callbacks rather than access
-  //   callbacks, because access callbacks are also invoked during menu link
-  //   generation. Add token support to routing: http://drupal.org/node/755584.
-  $token = Drupal::request()->query->get('token');
-  if (!isset($token) || !drupal_valid_token($token, 'overlay')) {
-    throw new AccessDeniedHttpException();
-  }
-
-  Drupal::service('user.data')->set('overlay', $user->uid, 'message_dismissed', 1);
-  drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.'));
-  // Destination is normally given. Go to the user profile as a fallback.
-  return new RedirectResponse(url('user/' . $user->uid . '/edit', array('absolute' => TRUE)));
-}
-
-/**
  * Returns a renderable array representing a message for disabling the overlay.
  *
  * If the current user can access the overlay and has not previously indicated
diff --git a/core/modules/overlay/overlay.routing.yml b/core/modules/overlay/overlay.routing.yml
new file mode 100644
index 0000000..9f22f56
--- /dev/null
+++ b/core/modules/overlay/overlay.routing.yml
@@ -0,0 +1,7 @@
+overlay_message:
+  pattern: '/overlay/dismiss-message'
+  defaults:
+    _controller: '\Drupal\overlay\Controller\OverlayController::overlayMessage'
+  requirements:
+    _access_overlay_dismiss_message: 'TRUE'
+
diff --git a/core/modules/overlay/overlay.services.yml b/core/modules/overlay/overlay.services.yml
index b15e6cd..aed4378 100644
--- a/core/modules/overlay/overlay.services.yml
+++ b/core/modules/overlay/overlay.services.yml
@@ -4,3 +4,8 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@content_negotiation', '@user.data', '@url_generator']
+
+  access_check.overlay.dismiss_message:
+    class: Drupal\overlay\Access\DismissMessageAccessCheck
+    tags:
+      - { name: access_check }
