diff --git a/core/core.services.yml b/core/core.services.yml
index afccb31..940113f 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1396,7 +1396,7 @@ services:
     class: Drupal\Core\Session\PermissionsHashGenerator
     arguments: ['@private_key', '@cache.default', '@cache.static']
   current_user:
-    class: Drupal\Core\Session\AccountProxy
+    class: Drupal\Core\Session\CurrentUser
   session_configuration:
     class: Drupal\Core\Session\SessionConfiguration
     arguments: ['%session.storage.options%']
diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php
index de15945..452d70c 100644
--- a/core/lib/Drupal/Core/Access/AccessResult.php
+++ b/core/lib/Drupal/Core/Access/AccessResult.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Config\ConfigBase;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Session\CurrentUserInterface;
 
 /**
  * Value object for passing an access result with cacheability metadata.
@@ -105,7 +106,7 @@ public static function forbiddenIf($condition) {
    *   isNeutral() will be TRUE.
    */
   public static function allowedIfHasPermission(AccountInterface $account, $permission) {
-    return static::allowedIf($account->hasPermission($permission))->addCacheContexts(['user.permissions']);
+    return static::allowedIf($account->hasPermission($permission))->cachePerPermissions($account);
   }
 
   /**
@@ -146,7 +147,11 @@ public static function allowedIfHasPermissions(AccountInterface $account, array
       }
     }
 
-    return static::allowedIf($access)->addCacheContexts(empty($permissions) ? [] : ['user.permissions']);
+    $result = static::allowedIf($access);
+    if (!empty($permissions)) {
+      $result->cachePerPermissions($account);
+    }
+    return $result;
   }
 
   /**
@@ -235,19 +240,61 @@ public function setCacheMaxAge($max_age) {
    *
    * @return $this
    */
-  public function cachePerPermissions() {
-    $this->addCacheContexts(array('user.permissions'));
+  public function cachePerPermissions(AccountInterface $account = NULL) {
+    // When the current user is access checked then we can use the cache
+    // contexts mechanism for more fine grained caching. Otherwise, the right
+    // tags needs to be added manually.
+    if ($this->isCurrentUser($account)) {
+      $this->addCacheContexts(array('user.permissions'));
+    }
+    else {
+      $this->addCacheableDependency($account);
+      $this->addCacheTags(array_map(function ($rid) { return  "config:user.role.$rid";}, (array) $account->getRoles()));
+    }
     return $this;
   }
 
   /**
-   * Convenience method, adds the "user" cache context.
+   * Indicate this context is only cacheable until an account changes.
+   *
+   * @param \Drupal\Core\Session\AccountInterface|NULL $account
+   *   An optional account. The optional part is deprecated, in Drupal 9,
+   *   $account will be mandatory.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Core\Access\AccessResult::cachePerCurrentUser
+   */
+  public function cachePerUser(AccountInterface $account = NULL) {
+    if (!isset($account) || $this->isCurrentUser($account)) {
+      $this->addCacheContexts(array('user'));
+    }
+    else {
+      $this->addCacheableDependency($account);
+    }
+    return $this;
+  }
+
+  /**
+   * Convenience method, adds the "user" cache context for the current user.
    *
    * @return $this
    */
-  public function cachePerUser() {
+  public function cachePerCurrentUser() {
     $this->addCacheContexts(array('user'));
-    return $this;
+  }
+
+  /**
+   * This checks whether an account is the current user.
+   *
+   * @param \Drupal\Core\Session\AccountInterface|NULL $account
+   *   An optional account.
+   *
+   * @return bool
+   *   TRUE if the user is the current user, FALSE if not.
+   */
+  protected function isCurrentUser(AccountInterface $account = NULL) {
+    return !isset($account) || ($account instanceof CurrentUserInterface && $account->isCurrentUser());
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Session/AccountProxy.php b/core/lib/Drupal/Core/Session/AccountProxy.php
index 4d6db57..0906192 100644
--- a/core/lib/Drupal/Core/Session/AccountProxy.php
+++ b/core/lib/Drupal/Core/Session/AccountProxy.php
@@ -190,5 +190,4 @@ public function setInitialAccountId($account_id) {
   protected function loadUserEntity($account_id) {
     return \Drupal::entityManager()->getStorage('user')->load($account_id);
   }
-
 }
diff --git a/core/lib/Drupal/Core/Session/CurrentUser.php b/core/lib/Drupal/Core/Session/CurrentUser.php
new file mode 100644
index 0000000..bb807df
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/CurrentUser.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\CurrentUser.
+ */
+
+namespace Drupal\Core\Session;
+
+class CurrentUser extends AccountProxy implements CurrentUserInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isCurrentUser() {
+    return TRUE;
+  }
+}
diff --git a/core/lib/Drupal/Core/Session/CurrentUserInterface.php b/core/lib/Drupal/Core/Session/CurrentUserInterface.php
new file mode 100644
index 0000000..23ba39d
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/CurrentUserInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\CurrentUserInterface.
+ */
+
+namespace Drupal\Core\Session;
+
+interface CurrentUserInterface {
+
+  /**
+   * Whether this is the current user.
+   *
+   * @return bool
+   *   TRUE when this object is the current user.
+   */
+  public function isCurrentUser();
+
+}
diff --git a/core/modules/comment/src/CommentAccessControlHandler.php b/core/modules/comment/src/CommentAccessControlHandler.php
index 6e4072d..5a9c1b9 100644
--- a/core/modules/comment/src/CommentAccessControlHandler.php
+++ b/core/modules/comment/src/CommentAccessControlHandler.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\node\Plugin\views\filter\Access;
 
 /**
  * Defines the access control handler for the comment entity type.
@@ -45,7 +46,7 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
           ->andIf($entity->getCommentedEntity()->access($operation, $account, TRUE));
 
       case 'update':
-        return AccessResult::allowedIf($account->id() && $account->id() == $entity->getOwnerId() && $entity->isPublished() && $account->hasPermission('edit own comments'))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($entity);
+        return AccessResult::allowedIf($account->id() && $account->id() == $entity->getOwnerId() && $entity->isPublished() && $account->hasPermission('edit own comments'))->cachePerPermissions()->cachePerUser($account)->cacheUntilEntityChanges($entity);
 
       default:
         // No opinion.
diff --git a/core/modules/contact/src/Access/ContactPageAccess.php b/core/modules/contact/src/Access/ContactPageAccess.php
index 6b78784..5312a0b 100644
--- a/core/modules/contact/src/Access/ContactPageAccess.php
+++ b/core/modules/contact/src/Access/ContactPageAccess.php
@@ -67,7 +67,7 @@ public function access(UserInterface $user, AccountInterface $account) {
 
     // Users may not contact themselves by default, hence this requires user
     // granularity for caching.
-    $access = AccessResult::neutral()->cachePerUser();
+    $access = AccessResult::neutral()->cachePerUser($account);
     if ($account->id() == $contact_account->id()) {
       return $access;
     }
diff --git a/core/modules/filter/src/Tests/FilterFormatAccessTest.php b/core/modules/filter/src/Tests/FilterFormatAccessTest.php
index 590a337..b3858cc 100644
--- a/core/modules/filter/src/Tests/FilterFormatAccessTest.php
+++ b/core/modules/filter/src/Tests/FilterFormatAccessTest.php
@@ -127,9 +127,9 @@ function testFormatPermissions() {
     // which they were granted access.
     $fallback_format = entity_load('filter_format', filter_fallback_format());
     $this->assertTrue($this->allowedFormat->access('use', $this->webUser), 'A regular user has access to use a text format they were granted access to.');
-    $this->assertEqual(AccessResult::allowed()->addCacheContexts(['user.permissions']), $this->allowedFormat->access('use', $this->webUser, TRUE), 'A regular user has access to use a text format they were granted access to.');
+    $this->assertEqual(AccessResult::allowed()->cachePerPermissions($this->webUser), $this->allowedFormat->access('use', $this->webUser, TRUE), 'A regular user has access to use a text format they were granted access to.');
     $this->assertFalse($this->disallowedFormat->access('use', $this->webUser), 'A regular user does not have access to use a text format they were not granted access to.');
-    $this->assertEqual(AccessResult::neutral()->cachePerPermissions(), $this->disallowedFormat->access('use', $this->webUser, TRUE), 'A regular user does not have access to use a text format they were not granted access to.');
+    $this->assertEqual(AccessResult::neutral()->cachePerPermissions($this->webUser), $this->disallowedFormat->access('use', $this->webUser, TRUE), 'A regular user does not have access to use a text format they were not granted access to.');
     $this->assertTrue($fallback_format->access('use', $this->webUser), 'A regular user has access to use the fallback format.');
     $this->assertEqual(AccessResult::allowed(), $fallback_format->access('use', $this->webUser, TRUE), 'A regular user has access to use the fallback format.');
 
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 2479a29..ed45838 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -337,7 +337,7 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, \Drupal\Core\Se
         return AccessResult::allowed()->cachePerPermissions();
       }
       else {
-        return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
+        return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content') && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser($account)->cacheUntilEntityChanges($node);
       }
 
     case 'delete':
@@ -345,7 +345,7 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, \Drupal\Core\Se
         return AccessResult::allowed()->cachePerPermissions();
       }
       else {
-        return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
+        return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content') && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser($account)->cacheUntilEntityChanges($node);
       }
 
     default:
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index a54fc0a..c2ed0e7 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -900,7 +900,7 @@ function node_form_system_themes_admin_form_submit($form, FormStateInterface $fo
 /**
  * Implements hook_node_access().
  */
-function node_node_access(NodeInterface $node, $op, $account) {
+function node_node_access(NodeInterface $node, $op, AccountInterface $account) {
   $type = $node->bundle();
 
   switch ($op) {
@@ -912,7 +912,7 @@ function node_node_access(NodeInterface $node, $op, $account) {
         return AccessResult::allowed()->cachePerPermissions();
       }
       else {
-        return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
+        return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content') && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser($account)->cacheUntilEntityChanges($node);
       }
 
     case 'delete':
@@ -920,7 +920,7 @@ function node_node_access(NodeInterface $node, $op, $account) {
         return AccessResult::allowed()->cachePerPermissions();
       }
       else {
-        return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
+        return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content') && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser($account)->cacheUntilEntityChanges($node);
       }
 
     default:
diff --git a/core/modules/node/src/NodeAccessControlHandler.php b/core/modules/node/src/NodeAccessControlHandler.php
index 490846f..1ddd305 100644
--- a/core/modules/node/src/NodeAccessControlHandler.php
+++ b/core/modules/node/src/NodeAccessControlHandler.php
@@ -105,7 +105,7 @@ protected function checkAccess(EntityInterface $node, $operation, AccountInterfa
 
     // Check if authors can view their own unpublished nodes.
     if ($operation === 'view' && !$status && $account->hasPermission('view own unpublished content') && $account->isAuthenticated() && $account->id() == $uid) {
-      return AccessResult::allowed()->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
+      return AccessResult::allowed()->cachePerPermissions()->cachePerUser($account)->cacheUntilEntityChanges($node);
     }
 
     // Evaluate node grants.
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index 066afcc..fe0a724 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -103,7 +103,7 @@ function shortcut_set_switch_access($account = NULL) {
     return AccessResult::allowed()->cachePerPermissions();
   }
   elseif ($user->id() == $account->id()) {
-    return AccessResult::allowed()->cachePerPermissions()->cachePerUser();
+    return AccessResult::allowed()->cachePerPermissions()->cachePerUser($account);
   }
 
   // No opinion.
diff --git a/core/modules/tracker/src/Access/ViewOwnTrackerAccessCheck.php b/core/modules/tracker/src/Access/ViewOwnTrackerAccessCheck.php
index c4925dc..cfbe631 100644
--- a/core/modules/tracker/src/Access/ViewOwnTrackerAccessCheck.php
+++ b/core/modules/tracker/src/Access/ViewOwnTrackerAccessCheck.php
@@ -29,6 +29,6 @@ class ViewOwnTrackerAccessCheck implements AccessInterface {
    *   The access result.
    */
   public function access(AccountInterface $account, UserInterface $user) {
-    return AccessResult::allowedIf($user && $account->isAuthenticated() && ($user->id() == $account->id()))->cachePerUser();
+    return AccessResult::allowedIf($user && $account->isAuthenticated() && ($user->id() == $account->id()))->cachePerUser($account);
   }
 }
diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php
index 1dcdefb..5307401 100644
--- a/core/modules/user/src/Entity/User.php
+++ b/core/modules/user/src/Entity/User.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Session\CurrentUserInterface;
 use Drupal\user\RoleInterface;
 use Drupal\user\UserInterface;
 
@@ -61,7 +62,7 @@
  *   common_reference_target = TRUE
  * )
  */
-class User extends ContentEntityBase implements UserInterface {
+class User extends ContentEntityBase implements UserInterface, CurrentUserInterface {
 
   use EntityChangedTrait;
 
@@ -573,4 +574,11 @@ public static function getAllowedConfigurableLanguageCodes() {
     return array_keys(\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_CONFIGURABLE));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isCurrentUser() {
+    return $this->id() === \Drupal::currentUser()->id();
+  }
+
 }
diff --git a/core/modules/user/src/UserAccessControlHandler.php b/core/modules/user/src/UserAccessControlHandler.php
index ae52b2b..101591a 100644
--- a/core/modules/user/src/UserAccessControlHandler.php
+++ b/core/modules/user/src/UserAccessControlHandler.php
@@ -51,11 +51,11 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
 
       case 'update':
         // Users can always edit their own account.
-        return AccessResult::allowedIf($account->id() == $entity->id())->cachePerUser();
+        return AccessResult::allowedIf($account->id() == $entity->id())->cachePerUser($account);
 
       case 'delete':
         // Users with 'cancel account' permission can cancel their own account.
-        return AccessResult::allowedIf($account->id() == $entity->id() && $account->hasPermission('cancel account'))->cachePerPermissions()->cachePerUser();
+        return AccessResult::allowedIf($account->id() == $entity->id() && $account->hasPermission('cancel account'))->cachePerPermissions()->cachePerUser($account);
     }
 
     // No opinion.
@@ -90,7 +90,7 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_
         // Allow edit access for the own user name if the permission is
         // satisfied.
         if ($is_own_account && $account->hasPermission('change own username')) {
-          return AccessResult::allowed()->cachePerPermissions()->cachePerUser();
+          return AccessResult::allowed()->cachePerPermissions()->cachePerUser($account);
         }
         else {
           return AccessResult::forbidden();
@@ -103,7 +103,7 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_
         // Allow view access to own mail address and other personalization
         // settings.
         if ($operation == 'view') {
-          return $is_own_account ? AccessResult::allowed()->cachePerUser() : AccessResult::forbidden();
+          return $is_own_account ? AccessResult::allowed()->cachePerUser($account) : AccessResult::forbidden();
         }
         // Anyone that can edit the user can also edit this field.
         return AccessResult::allowed()->cachePerPermissions();
diff --git a/core/modules/user/tests/src/Unit/PermissionAccessCheckTest.php b/core/modules/user/tests/src/Unit/PermissionAccessCheckTest.php
index 7ad78ef..a6f5938 100644
--- a/core/modules/user/tests/src/Unit/PermissionAccessCheckTest.php
+++ b/core/modules/user/tests/src/Unit/PermissionAccessCheckTest.php
@@ -75,7 +75,7 @@ public function providerTestAccess() {
    */
   public function testAccess($requirements, $access, array $contexts = []) {
     $access_result = AccessResult::allowedIf($access)->addCacheContexts($contexts);
-    $user = $this->getMock('Drupal\Core\Session\AccountInterface');
+    $user = $this->getMock('Drupal\Core\Session\CurrentUser');
     $user->expects($this->any())
       ->method('hasPermission')
       ->will($this->returnValueMap([
@@ -84,6 +84,9 @@ public function testAccess($requirements, $access, array $contexts = []) {
           ['other', FALSE]
         ]
       ));
+    $user->expects($this->any())
+      ->method('isCurrentUser')
+      ->willReturn(TRUE);
     $route = new Route('', [], $requirements);
 
     $this->assertEquals($access_result, $this->accessCheck->access($route, $user));
diff --git a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php
index 287c344..29a8cbe 100644
--- a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php
@@ -334,11 +334,11 @@ public function testCacheMaxAge() {
    * @covers ::allowedIfHasPermission
    */
   public function testCacheContexts() {
-    $verify = function (AccessResult $access, array $contexts) {
+    $verify = function (AccessResult $access, array $contexts, $max_age = Cache::PERMANENT) {
       $this->assertFalse($access->isAllowed());
       $this->assertFalse($access->isForbidden());
       $this->assertTrue($access->isNeutral());
-      $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
+      $this->assertSame($max_age, $access->getCacheMaxAge());
       $this->assertSame($contexts, $access->getCacheContexts());
       $this->assertSame([], $access->getCacheTags());
     };
@@ -390,7 +390,7 @@ public function testCacheContexts() {
     $this->assertEquals($a, $c);
 
     // ::allowIfHasPermission and ::allowedIfHasPermission convenience methods.
-    $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
+    $account = $this->getMock('\Drupal\Core\Session\CurrentUser');
     $account->expects($this->any())
       ->method('hasPermission')
       ->with('may herd llamas')
@@ -398,9 +398,23 @@ public function testCacheContexts() {
     $contexts = array('user.permissions');
 
     // Verify the object when using the ::allowedIfHasPermission() convenience
-    // static method.
+    // static method and no current user.
     $b = AccessResult::allowedIfHasPermission($account, 'may herd llamas');
-    $verify($b, $contexts);
+    $verify($b, [], 0);
+
+    // Verify the object when using the ::allowedIfHasPermission() convenience
+    // static method and using the current user and an arbitrary user.ph.
+    $id = mt_rand(10, 20);
+    $original_account = clone $account;
+    foreach ([Cache::PERMANENT => TRUE, FALSE] as $max_age => $current_user) {
+      $account
+        ->expects($this->once())
+        ->method('isCurrentUser')
+        ->willReturn($current_user);
+      $b = AccessResult::allowedIfHasPermission($account, 'may herd llamas');
+      $verify($b, $current_user ? $contexts : [], $max_age);
+      $account = $original_account;
+    }
   }
 
   /**
@@ -874,7 +888,9 @@ public function testAllowedIfHasPermissions($permissions, $conjunction, AccessRe
     }
 
     $access_result = AccessResult::allowedIfHasPermissions($account, $permissions, $conjunction);
-    $this->assertEquals($expected_access, $access_result);
+    $this->assertEquals($expected_access->isAllowed(), $access_result->isAllowed());
+    $this->assertEquals($expected_access->isNeutral(), $access_result->isNeutral());
+    $this->assertEquals($expected_access->isForbidden(), $access_result->isForbidden());
   }
 
   /**
