diff -r 6490bd644de2 django/core/context_processors.py
--- a/django/core/context_processors.py	Thu Oct 08 23:40:00 2009 +0100
+++ b/django/core/context_processors.py	Wed Oct 14 13:08:40 2009 +0100
@@ -8,6 +8,7 @@
 """
 
 from django.conf import settings
+from django.template.utils import ContextLazyObject
 
 def auth(request):
     """
@@ -17,15 +18,31 @@
     If there is no 'user' attribute in the request, uses AnonymousUser (from
     django.contrib.auth).
     """
-    if hasattr(request, 'user'):
-        user = request.user
-    else:
-        from django.contrib.auth.models import AnonymousUser
-        user = AnonymousUser()
+    # If we access request.user, request.session is accessed, which results in
+    # 'Vary: Cookie' being sent in every request that uses this context
+    # processor, which can easily be every request on a site if
+    # TEMPLATE_CONTEXT_PROCESSORS has this context processor added.  This kills
+    # the ability to cache.  So, we carefully ensure these attributes are lazy.
+    # We don't use django.utils.functional.lazy(), because that requires knowing
+    # the class of the object we want to proxy, which could break with custom
+    # auth backends.  LazyObject is a less complete but more flexible solution
+    # that is good enough for wrapper 'User'. Also, lazy doesn't seem to work
+    # fully with PermWrapper, so we have modified that to allow laziness.
+    def get_user():
+        if hasattr(request, 'user'):
+            return request.user
+        else:
+            from django.contrib.auth.models import AnonymousUser
+            return AnonymousUser()
+
+    def messages():
+        for m in get_user().get_and_delete_messages():
+            yield m
+
     return {
-        'user': user,
-        'messages': user.get_and_delete_messages(),
-        'perms': PermWrapper(user),
+        'user': ContextLazyObject(get_user),
+        'messages': messages(),
+        'perms':  PermWrapper(get_user),
     }
 
 def debug(request):
@@ -75,11 +92,18 @@
 
 class PermWrapper(object):
     def __init__(self, user):
-        self.user = user
+        self._user = user
+
+    def user(self):
+        # Allow callable for laziness
+        if callable(self._user):
+            self._user = self._user()
+        return self._user
+    user = property(user)
 
     def __getitem__(self, module_name):
         return PermLookupDict(self.user, module_name)
-        
+
     def __iter__(self):
         # I am large, I contain multitudes.
         raise TypeError("PermWrapper is not iterable.")
diff -r 6490bd644de2 django/template/utils.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/template/utils.py	Wed Oct 14 13:08:40 2009 +0100
@@ -0,0 +1,24 @@
+from django.utils.functional import LazyObject
+
+class ContextLazyObject(LazyObject):
+    """
+    A lazy object initialised from any function, useful for lazily
+    adding things to the Context.
+
+    Works best for compound objects of unknown type where you are only
+    interested in the attributes, and won't need things like str(obj).  For
+    simple objects of known type, use django.utils.functional.lazy
+
+    For sequences, use a generator instead.
+    """
+    def __init__(self, func):
+        """
+        Pass in a callable that returns the actual value to be used
+        """
+        self.__dict__['_setupfunc'] = func
+        # For some reason, we have to inline LazyObject.__init__ here to avoid
+        # recursion
+        self._wrapped = None
+
+    def _setup(self):
+        self._wrapped = self._setupfunc()
diff -r 6490bd644de2 tests/regressiontests/context_processors/fixtures/context-processors-users.xml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/context_processors/fixtures/context-processors-users.xml	Wed Oct 14 13:08:40 2009 +0100
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+    <object pk="100" model="auth.user">
+        <field type="CharField" name="username">super</field>
+        <field type="CharField" name="first_name">Super</field>
+        <field type="CharField" name="last_name">User</field>
+        <field type="CharField" name="email">super@example.com</field>
+        <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+        <field type="BooleanField" name="is_staff">True</field>
+        <field type="BooleanField" name="is_active">True</field>
+        <field type="BooleanField" name="is_superuser">True</field>
+        <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+        <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+        <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+        <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+    </object>
+</django-objects>
diff -r 6490bd644de2 tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html	Wed Oct 14 13:08:40 2009 +0100
@@ -0,0 +1,1 @@
+{{ user.id }}
diff -r 6490bd644de2 tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html	Wed Oct 14 13:08:40 2009 +0100
@@ -0,0 +1,1 @@
+{% if perms.auth %}Has auth permissions{% endif %}
diff -r 6490bd644de2 tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html	Wed Oct 14 13:08:40 2009 +0100
@@ -0,0 +1,1 @@
+{% if session_accessed %}Session accessed{% else %}Session not accessed{% endif %}
diff -r 6490bd644de2 tests/regressiontests/context_processors/tests.py
--- a/tests/regressiontests/context_processors/tests.py	Thu Oct 08 23:40:00 2009 +0100
+++ b/tests/regressiontests/context_processors/tests.py	Wed Oct 14 13:08:40 2009 +0100
@@ -36,3 +36,31 @@
         self.assertContains(response, url)
         response = self.client.post(url, {'path': '/blah/'})
         self.assertContains(response, url)
+
+class AuthContextProcessorTests(TestCase):
+    """
+    Tests for the ``django.core.context_processors.auth`` processor
+    """
+    urls = 'regressiontests.context_processors.urls'
+    fixtures = ['context-processors-users.xml']
+
+    def test_session_not_accessed(self):
+        """
+        Tests that the session is not accessed simply by including
+        the auth context processor
+        """
+        response = self.client.get('/auth_processor_no_attr_access/')
+        self.assertContains(response, "Session not accessed")
+
+    def test_session_is_accessed(self):
+        """
+        Tests that the session is accessed if the auth context processor
+        is used and relevant attributes accessed.
+        """
+        response = self.client.get('/auth_processor_attr_access/')
+        self.assertContains(response, "Session accessed")
+
+    def test_perms_attrs(self):
+        self.client.login(username='super', password='secret')
+        response = self.client.get('/auth_processor_perms/')
+        self.assertContains(response, "Has auth permissions")
diff -r 6490bd644de2 tests/regressiontests/context_processors/urls.py
--- a/tests/regressiontests/context_processors/urls.py	Thu Oct 08 23:40:00 2009 +0100
+++ b/tests/regressiontests/context_processors/urls.py	Wed Oct 14 13:08:40 2009 +0100
@@ -5,4 +5,7 @@
 
 urlpatterns = patterns('',
     (r'^request_attrs/$', views.request_processor),
+    (r'^auth_processor_no_attr_access/$', views.auth_processor_no_attr_access),
+    (r'^auth_processor_attr_access/$', views.auth_processor_attr_access),
+    (r'^auth_processor_perms/$', views.auth_processor_perms),
 )
diff -r 6490bd644de2 tests/regressiontests/context_processors/views.py
--- a/tests/regressiontests/context_processors/views.py	Thu Oct 08 23:40:00 2009 +0100
+++ b/tests/regressiontests/context_processors/views.py	Wed Oct 14 13:08:40 2009 +0100
@@ -6,3 +6,20 @@
 def request_processor(request):
     return render_to_response('context_processors/request_attrs.html',
         RequestContext(request, {}, processors=[context_processors.request]))
+
+def auth_processor_no_attr_access(request):
+    r1 = render_to_response('context_processors/auth_attrs_no_access.html',
+        RequestContext(request, {}, processors=[context_processors.auth]))
+    # *After* rendering, we check whether the session was accessed
+    return render_to_response('context_processors/auth_attrs_test_access.html',
+        {'session_accessed':request.session.accessed})
+
+def auth_processor_attr_access(request):
+    r1 = render_to_response('context_processors/auth_attrs_access.html',
+        RequestContext(request, {}, processors=[context_processors.auth]))
+    return render_to_response('context_processors/auth_attrs_test_access.html',
+        {'session_accessed':request.session.accessed})
+
+def auth_processor_perms(request):
+    return render_to_response('context_processors/auth_attrs_perms.html',
+        RequestContext(request, {}, processors=[context_processors.auth]))
