diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py
index d5f9186902..a40e93a7f5 100644
--- a/django/views/generic/detail.py
+++ b/django/views/generic/detail.py
@@ -17,6 +17,7 @@ class SingleObjectMixin(ContextMixin):
     context_object_name = None
     slug_url_kwarg = 'slug'
     pk_url_kwarg = 'pk'
+    query_pk_and_slug = False
 
     def get_object(self, queryset=None):
         """
@@ -37,12 +38,12 @@ class SingleObjectMixin(ContextMixin):
             queryset = queryset.filter(pk=pk)
 
         # Next, try looking up by slug.
-        elif slug is not None:
+        if slug is not None and (pk is None or self.query_pk_and_slug):
             slug_field = self.get_slug_field()
             queryset = queryset.filter(**{slug_field: slug})
 
         # If none of those are defined, it's an error.
-        else:
+        if pk is None and slug is None:
             raise AttributeError("Generic detail view %s must be called with "
                                  "either an object pk or a slug."
                                  % self.__class__.__name__)
diff --git a/docs/ref/class-based-views/mixins-single-object.txt b/docs/ref/class-based-views/mixins-single-object.txt
index 39f0516669..2f48841463 100644
--- a/docs/ref/class-based-views/mixins-single-object.txt
+++ b/docs/ref/class-based-views/mixins-single-object.txt
@@ -50,16 +50,40 @@ SingleObjectMixin
 
         Designates the name of the variable to use in the context.
 
+    .. attribute:: query_pk_and_slug
+
+        .. versionadded:: 1.8
+
+        If ``True``, causes :meth:`get_object()` to perform its lookup using
+        both the primary key and the slug. Defaults to ``False``.
+
+        This attribute can help mitigate `insecure direct object reference`_
+        attacks. When applications allow access to individual objects by a
+        sequential primary key, an attacker could brute-force guess all URLs;
+        thereby obtaining a list of all objects in the application. If users
+        with access to individual objects should be prevented from obtaining
+        this list, setting ``query_pk_and_slug`` to ``True`` will help prevent
+        the guessing of URLs as each URL will require two correct,
+        non-sequential arguments. Simply using a unique slug may serve the same
+        purpose, but this scheme allows you to have non-unique slugs.
+
+        .. _insecure direct object reference: https://www.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References
+
     .. method:: get_object(queryset=None)
 
-        Returns the single object that this view will display. If
-        ``queryset`` is provided, that queryset will be used as the
-        source of objects; otherwise, :meth:`get_queryset` will be used.
-        ``get_object()`` looks for a :attr:`pk_url_kwarg` argument in the
-        arguments to the view; if this argument is found, this method performs
-        a primary-key based lookup using that value. If this argument is not
-        found, it looks for a :attr:`slug_url_kwarg` argument, and performs a
-        slug lookup using the :attr:`slug_field`.
+        Returns the single object that this view will display. If ``queryset``
+        is provided, that queryset will be used as the source of objects;
+        otherwise, :meth:`get_queryset` will be used. ``get_object()`` looks
+        for a :attr:`pk_url_kwarg` argument in the arguments to the view; if
+        this argument is found, this method performs a primary-key based lookup
+        using that value. If this argument is not found, it looks for a
+        :attr:`slug_url_kwarg` argument, and performs a slug lookup using the
+        :attr:`slug_field`.
+
+        .. versionchanged:: 1.8
+
+            When :attr:`query_pk_and_slug` is ``True``, ``get_object()`` will
+            perform its lookup using both the primary key and the slug.
 
     .. method:: get_queryset()
 
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index 3eec697364..01e48b75e4 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -225,6 +225,12 @@ Generic Views
   :attr:`~django.views.generic.list.MultipleObjectMixin.ordering` or overriding
   :meth:`~django.views.generic.list.MultipleObjectMixin.get_ordering()`.
 
+* The new :attr:`SingleObjectMixin.query_pk_and_slug
+  <django.views.generic.detail.SingleObjectMixin.query_pk_and_slug>`
+  attribute allows changing the behavior of
+  :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()`
+  so that it'll perform its lookup using both the primary key and the slug.
+
 Internationalization
 ^^^^^^^^^^^^^^^^^^^^
 
diff --git a/tests/generic_views/test_detail.py b/tests/generic_views/test_detail.py
index 3f535e6f91..ca747351d6 100644
--- a/tests/generic_views/test_detail.py
+++ b/tests/generic_views/test_detail.py
@@ -53,6 +53,34 @@ class DetailViewTest(TestCase):
         self.assertEqual(res.context['author'], Author.objects.get(slug='scott-rosenberg'))
         self.assertTemplateUsed(res, 'generic_views/author_detail.html')
 
+    def test_detail_by_pk_ignore_slug(self):
+        author = Author.objects.get(pk=1)
+        res = self.client.get('/detail/author/bypkignoreslug/1-roberto-bolano/')
+        self.assertEqual(res.status_code, 200)
+        self.assertEqual(res.context['object'], author)
+        self.assertEqual(res.context['author'], author)
+        self.assertTemplateUsed(res, 'generic_views/author_detail.html')
+
+    def test_detail_by_pk_ignore_slug_mismatch(self):
+        author = Author.objects.get(pk=1)
+        res = self.client.get('/detail/author/bypkignoreslug/1-scott-rosenberg/')
+        self.assertEqual(res.status_code, 200)
+        self.assertEqual(res.context['object'], author)
+        self.assertEqual(res.context['author'], author)
+        self.assertTemplateUsed(res, 'generic_views/author_detail.html')
+
+    def test_detail_by_pk_and_slug(self):
+        author = Author.objects.get(pk=1)
+        res = self.client.get('/detail/author/bypkandslug/1-roberto-bolano/')
+        self.assertEqual(res.status_code, 200)
+        self.assertEqual(res.context['object'], author)
+        self.assertEqual(res.context['author'], author)
+        self.assertTemplateUsed(res, 'generic_views/author_detail.html')
+
+    def test_detail_by_pk_and_slug_mismatch_404(self):
+        res = self.client.get('/detail/author/bypkandslug/1-scott-rosenberg/')
+        self.assertEqual(res.status_code, 404)
+
     def test_verbose_name(self):
         res = self.client.get('/detail/artist/1/')
         self.assertEqual(res.status_code, 200)
diff --git a/tests/generic_views/urls.py b/tests/generic_views/urls.py
index b88b33892e..f4965e2317 100644
--- a/tests/generic_views/urls.py
+++ b/tests/generic_views/urls.py
@@ -36,6 +36,10 @@ urlpatterns = [
         views.AuthorDetail.as_view()),
     url(r'^detail/author/bycustomslug/(?P<foo>[\w-]+)/$',
         views.AuthorDetail.as_view(slug_url_kwarg='foo')),
+    url(r'^detail/author/bypkignoreslug/(?P<pk>[0-9]+)-(?P<slug>[\w-]+)/$',
+        views.AuthorDetail.as_view()),
+    url(r'^detail/author/bypkandslug/(?P<pk>[0-9]+)-(?P<slug>[\w-]+)/$',
+        views.AuthorDetail.as_view(query_pk_and_slug=True)),
     url(r'^detail/author/(?P<pk>[0-9]+)/template_name_suffix/$',
         views.AuthorDetail.as_view(template_name_suffix='_view')),
     url(r'^detail/author/(?P<pk>[0-9]+)/template_name/$',