Django REST framework API 指南(13):認證

wcode發表於2018-03-15

官方原文連結
本系列文章 github 地址
轉載請註明出處

身份驗證

身份驗證是將傳入請求與一組識別憑證(例如請求的使用者或其簽名的令牌)相關聯的機制。然後,許可權和限制策略可以使用這些憑據來確定請求是否應該被允許。

REST framework 提供了許多開箱即用的身份驗證方案,同時也允許你實施自定義方案。

身份驗證始終在檢視的開始處執行,在執行許可權和限制檢查之前,在允許繼續執行任何其他程式碼之前。

request.user 屬性通常會設定為 contrib.auth 包的 User 類的一個例項。

request.auth 屬性用於其他身份驗證資訊,例如,它可以用來表示請求已簽名的身份驗證令牌。


注意: 不要忘記, 身份驗證本身不會(允許或不允許)傳入的請求,它只是標識請求的憑據。


如何確定身份驗證

認證方案總是被定義為一個類的列表。 REST framework 將嘗試使用列表中的每個類進行認證,並將使用成功認證的第一個類的返回值來設定 request.userrequest.auth

如果沒有類進行身份驗證,則將 request.user 設定為 django.contrib.auth.models.AnonymousUser 的例項,並將 request.auth 設定為 None.

可以使用 UNAUTHENTICATED_USERUNAUTHENTICATED_TOKEN 設定修改未經身份驗證的請求的 request.userrequest.auth 的值。

設定認證方案

預設的認證方案可以使用 DEFAULT_AUTHENTICATION_CLASSES setting 全域性設定。例如。

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    )
}
複製程式碼

您還可以使用基於 APIView 類的檢視,在每個檢視或每個檢視集的基礎上設定身份驗證方案。

from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated,)

    def get(self, request, format=None):
        content = {
            'user': unicode(request.user),  # `django.contrib.auth.User` instance.
            'auth': unicode(request.auth),  # None
        }
        return Response(content)
複製程式碼

或者,如果您將 @api_view 裝飾器與基於函式的檢視一起使用。

@api_view(['GET'])
@authentication_classes((SessionAuthentication, BasicAuthentication))
@permission_classes((IsAuthenticated,))
def example_view(request, format=None):
    content = {
        'user': unicode(request.user),  # `django.contrib.auth.User` instance.
        'auth': unicode(request.auth),  # None
    }
    return Response(content)
複製程式碼

未經授權和禁止響應

當未經身份驗證的請求被拒絕時,有兩種不同的錯誤程式碼可能是合適的。

  • [HTTP 401 Unauthorized][http401]
  • [HTTP 403 Permission Denied][http403]

HTTP 401 響應必須始終包含 WWW-Authenticate header,該 header 指示客戶端如何進行身份驗證。 HTTP 403 響應不包含 WWW-Authenticate header。

將使用哪種響應取決於認證方案。儘管可能正在使用多種認證方案,但只能使用一種方案來確定響應的型別。 在確定響應型別時使用檢視上設定的第一個認證類

請注意,當請求可以成功進行身份驗證時,仍然可能會因為許可權而被拒絕,在這種情況下,將始終使用 403 Permission Denied 響應,而不管身份驗證方案如何。

Apache mod_wsgi 特定的配置

請注意,如果使用 mod_wsgi 部署到 Apache,授權 header 預設情況下不會傳遞到 WSGI 應用程式,因為它假定認證將由 Apache 處理,而不是在應用程式級別處理。

如果您正在部署到 Apache 並使用任何基於非會話的身份驗證,則需要明確配置 mod_wsgi 以將所需的 headers 傳遞給應用程式。這可以通過在適當的上下文中指定 WSGIPassAuthorization 指令並將其設定為 'On' 來完成。

# this can go in either server config, virtual host, directory or .htaccess
WSGIPassAuthorization On
複製程式碼

API 參考

BasicAuthentication

該認證方案使用 HTTP Basic Authentication,並根據使用者的使用者名稱和密碼進行簽名。Basic Authentication 通常只適用於測試。

如果成功通過身份驗證,BasicAuthentication 將提供以下憑據。

  • request.user 是一個 Django User 實力.
  • request.authNone.

未經身份驗證的響應被拒絕將導致 HTTP 401 Unauthorized 的響應和相應的 WWW-Authenticate header。例如:

WWW-Authenticate: Basic realm="api"
複製程式碼

注意: 如果您在生產環境中使用 BasicAuthentication,則必須確保您的 API 僅可通過 https 訪問。您還應該確保您的 API 客戶端將始終在登入時重新請求使用者名稱和密碼,並且永遠不會將這些詳細資訊儲存到持久化儲存中。

TokenAuthentication

此認證方案使用簡單的基於令牌的 HTTP 認證方案。令牌身份驗證適用於 client-server 架構,例如本機桌面和移動客戶端。

要使用 TokenAuthentication 方案,您需要將認證類配置為包含 TokenAuthentication ,並在 INSTALLED_APPS 設定中另外包含 rest_framework.authtoken

INSTALLED_APPS = (
    ...
    'rest_framework.authtoken'
)
複製程式碼

注意: 確保在更改設定後執行 manage.py migraterest_framework.authtoken 應用程式提供 Django 資料庫遷移。


您還需要為您的使用者建立令牌。

from rest_framework.authtoken.models import Token

token = Token.objects.create(user=...)
print token.key
複製程式碼

對於客戶端進行身份驗證,令牌金鑰應包含在 Authorization HTTP header 中。關鍵字應以字串文字 “Token” 為字首,用空格分隔兩個字串。例如:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
複製程式碼

注意: 如果您想在 header 中使用不同的關鍵字(例如 Bearer),只需子類化 TokenAuthentication 並設定 keyword 類變數。

如果成功通過身份驗證,TokenAuthentication 將提供以下憑據。

  • request.user 是一個 Django User 例項.
  • request.auth 是一個 rest_framework.authtoken.models.Token 例項.

未經身份驗證的響應被拒絕將導致 HTTP 401 Unauthorized 的響應和相應的 WWW-Authenticate header。例如:

WWW-Authenticate: Token
複製程式碼

curl 命令列工具可能對測試令牌認證的 API 有用。例如:

curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'
複製程式碼

注意: 如果您在生產中使用 TokenAuthentication,則必須確保您的 API 只能通過 https 訪問。


生成令牌

通過使用訊號

如果您希望每個使用者都擁有一個自動生成的令牌,則只需捕捉使用者的 post_save 訊號即可。

from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)
複製程式碼

請注意,您需要確保將此程式碼片段放置在已安裝的 models.py 模組或 Django 啟動時將匯入的其他某個位置。

如果您已經建立了一些使用者,則可以為所有現有使用者生成令牌,例如:

from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token

for user in User.objects.all():
    Token.objects.get_or_create(user=user)
複製程式碼
通過暴露一個 API 端點

使用 TokenAuthentication 時,您可能希望為客戶提供一種機制,以獲取給定使用者名稱和密碼的令牌。 REST framework 提供了一個內建的檢視來支援這種行為。要使用它,請將 obtain_auth_token 檢視新增到您的 URLconf 中:

from rest_framework.authtoken import views
urlpatterns += [
    url(r'^api-token-auth/', views.obtain_auth_token)
]
複製程式碼

請注意,模式的 URL 部分可以是任何你想使用的。

當使用表單資料或 JSON 將有效的 usernamepassword 欄位釋出到檢視時, obtain_auth_token 檢視將返回 JSON 響應:

{ 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }
複製程式碼

請注意,預設的 obtain_auth_token 檢視顯式使用 JSON 請求和響應,而不是使用你設定的預設的渲染器和解析器類。

預設情況下,沒有許可權或限制應用於 obtain_auth_token 檢視。 如果您希望應用 throttling ,則需要重寫檢視類,並使用 throttle_classes 屬性包含它們。

如果你需要自定義 obtain_auth_token 檢視,你可以通過繼承 ObtainAuthToken 檢視類來實現,並在你的 url conf 中使用它。

例如,您可能會返回超出 token 值的其他使用者資訊:

from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response

class CustomAuthToken(ObtainAuthToken):

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data,
                                           context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        return Response({
            'token': token.key,
            'user_id': user.pk,
            'email': user.email
        })
複製程式碼

還有 urls.py:

urlpatterns += [
    url(r'^api-token-auth/', CustomAuthToken.as_view())
]
複製程式碼
使用 Django admin

也可以通過管理介面手動建立令牌。如果您使用的使用者群很大,我們建議您對 TokenAdmin 類進行修補以根據需要對其進行定製,更具體地說,將 user 欄位宣告為 raw_field

your_app/admin.py:

from rest_framework.authtoken.admin import TokenAdmin

TokenAdmin.raw_id_fields = ('user',)
複製程式碼

使用 Django manage.py 命令

從版本 3.6.4 開始,可以使用以下命令生成使用者令牌:

./manage.py drf_create_token <username>
複製程式碼

此命令將返回給定使用者的 API 令牌,如果它不存在則建立它:

Generated token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b for user user1
複製程式碼

如果您想重新生成令牌(例如,它已被洩漏),則可以傳遞一個附加引數:

./manage.py drf_create_token -r <username>
複製程式碼

SessionAuthentication

此認證方案使用 Django 的預設 session 後端進行認證。Session 身份驗證適用於與您的網站在同一會話環境中執行的 AJAX 客戶端。

如果成功通過身份驗證,則 SessionAuthentication 會提供以下憑據。

  • request.user 是一個 Django User 例項.
  • request.authNone.

未經身份驗證的響應被拒絕將導致 HTTP 403 Forbidden 響應。

如果您在 SessionAuthentication 中使用 AJAX 風格的 API,則需要確保為任何 “不安全” 的 HTTP 方法呼叫(例如 PUTPATCHPOSTDELETE 請求)包含有效的 CSRF 令牌。

警告: 建立登入頁面時應該始終使用 Django 的標準登入檢視。這將確保您的登入檢視得到適當的保護。

REST framework 中的 CSRF 驗證與標準 Django 略有不同,因為需要同時支援基於 session 和非基於 session 的身份驗證。這意味著只有經過身份驗證的請求才需要 CSRF 令牌,並且可以在沒有 CSRF 令牌的情況下傳送匿名請求。此行為不適用於應始終應用 CSRF 驗證的登入檢視。

RemoteUserAuthentication

這種身份驗證方案允許您將身份驗證委託給您的 Web 伺服器,該伺服器設定 REMOTE_USER 環境變數。

要使用它,你必須在你的 AUTHENTICATION_BACKENDS 設定中有 django.contrib.auth.backends.RemoteUserBackend (或者一個子類)。預設情況下,RemoteUserBackend 為不存在的使用者名稱建立 User 物件。要改變這個和其他行為,請參考 Django 文件。

如果成功通過身份驗證,RemoteUserAuthentication 將提供以下憑據:

  • request.user 是一個 Django User 例項.
  • request.authNone.

有關配置驗證方法的資訊,請參閱您的 Web 伺服器的文件,例如:

自定義身份認證

要實現自定義身份驗證方案,請繼承 BaseAuthentication 並重寫 .authenticate(self, request) 方法。如果認證成功,該方法應返回 (user, auth) 的二元組,否則返回 None

在某些情況下,您可能想要從 .authenticate() 方法引發 AuthenticationFailed 異常而不是返回 None

通常你應該採取的方法是:

  • 如果不嘗試認證,則返回 None。任何其他正在使用的身份驗證方案仍將被檢查。
  • 如果嘗試身份驗證但失敗了,請引發 AuthenticationFailed 異常。無論是否進行任何許可權檢查,都將立即返回錯誤響應,並且不再檢查任何其他身份驗證方案。

您也 可以 重寫 .authenticate_header(self, request) 方法。如果實現,它應該返回一個字串,該字串將用作 HTTP 401 Unauthorized 響應中的 WWW-Authenticate header 的值。

如果未覆蓋 .authenticate_header() 方法,那麼當未經身份驗證的請求被拒絕訪問時,身份驗證方案將返回 HTTP 403 Forbidden 響應。


Note: 當請求物件的 .user.auth 屬性呼叫您的自定義身份驗證器時,您可能會看到 AttributeError 作為 WrappedAttributeError 被重新引發。這對於防止原始異常被外部屬性訪問所抑制是必要的。Python 不會識別 AttributeError 來自您的自定義身份驗證器,而是會假設請求物件沒有 .user.auth 屬性。這些錯誤應該由您的驗證器修復或以其他方式處理。


舉個例子

下面的示例將根據名為 “X_USERNAME” 的自定義請求頭中的使用者名稱對任何傳入請求進行身份驗證。

from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions

class ExampleAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        username = request.META.get('X_USERNAME')
        if not username:
            return None

        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            raise exceptions.AuthenticationFailed('No such user')

        return (user, None)
複製程式碼

相關文章