Django REST framework 之 API認證

RESTful API 認證

  和 Web 應用不一樣,RESTful APIs 一般是無狀態的, 也就意味着不該使用 sessions 或 cookies, 所以每一個請求應附帶某種受權憑證,由於用戶受權狀態可能沒經過 sessions 或 cookies 維護, 經常使用的作法是每一個請求都發送一個祕密的 access token 來認證用戶, 因爲 access token 能夠惟一識別和認證用戶, API 請求應經過 HTTPS 來防止 man-in-the-middle(MitM)中間人攻擊。

一般有下面幾種方式來發送 access token:python

  • HTTP 基本認證:access token 看成用戶名發送,應用在 access token 可安全存在 API 使用端的場景, 例如,API 使用端是運行在一臺服務器上的程序。
  • 請求參數:access token 看成 API URL 請求參數發送,例如 https://example.com/users?access-token=xxxxxxxx, 因爲大多數服務器都會保存請求參數到日誌, 這種方式應主要用於JSONP 請求,由於它不能使用HTTP頭來發送access token
  • OAuth 2:使用者從認證服務器上獲取基於 OAuth2 協議的 access token, 而後經過 HTTP Bearer Tokens 發送到 API 服務器。

 

Django REST framework 認證

1、身份驗證  

    REST framework 提供了一些開箱即用的身份驗證方案,而且還容許你實現自定義方案。這裏須要明確一下用戶認證(Authentication)和用戶受權(Authorization)是兩個不一樣的概念,認證解決的是「有沒有」的問題,而受權解決的是「能不能」的問題。mysql

  • BasicAuthentication

該認證方案使用 HTTP Basic Authentication,並根據用戶的用戶名和密碼進行簽名。Basic Authentication 一般只適用於測試。sql

  • SessionAuthentication

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

  • TokenAuthentication

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

  • RemoteUserAuthentication

這種身份驗證方案容許您將身份驗證委託給您的 Web 服務器,該服務器設置 REMOTE_USER 環境變量。json

 

  默認的認證方案可使用DEFAULT_AUTHENTICATION_CLASSES全局設置,在settings.py文件配置。在默認狀況下,DRF開啓了 BasicAuthentication 與 SessionAuthentication 的認證。後端

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    )
}

  關於DRF,幾乎全部的配置都定義在MREST_FRAMEWORK變量中。另外,關於認證方式DRF默認會檢測配置在DEFAULT_AUTHENTICATION_CLASSES變量中的全部認證方式,只要有一個認證方式經過便可登陸成功。這裏的DEFAULT_AUTHENTICATION_CLASSES與Django中的MIDDLEWARE相似,在將request經過url映射到views以前,Django和DRF都會調用定義在MREST_FRAMEWORK變量中的類的一些方法。api

  另外,你還可使用基於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)

  另外,DRF的認證是在定義有權限類(permission_classes)的視圖下才有做用,且權限類(permission_classes)必需要求認證用戶才能訪問此視圖。若是沒有定義權限類(permission_classes),那麼也就意味着容許匿名用戶的訪問,天然牽涉不到認證相關的限制了。因此,通常在項目中的使用方式是在全局配置DEFAULT_AUTHENTICATION_CLASSES認證,而後會定義多個base views,根據不一樣的訪問需求來繼承不一樣的base views便可。服務器

from rest_framework.permissions import (
    IsAuthenticated,
    IsAdminUser,
    IsAuthenticatedOrReadOnly
)

class BaseView(APIView):
    '''普通用戶'''
    permission_classes = (
        IsOwnerOrReadOnly,
        IsAuthenticated
    )


class SuperUserpermissions(APIView):
    '''超級用戶'''
    permission_classes = (IsAdminUser,)


class NotLogin(APIView):
    '''匿名用戶'''
    pass

  另外,在先後端分離項目中通常不會使用 BasicAuthentication 與 SessionAuthentication 的認證方式。因此,咱們只須要關心 TokenAuthentication 認證方式便可。

2、TokenAuthentication

  要使用TokenAuthentication方案,須要將認證類配置爲包含TokenAuthentication

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    )
}

  並在INSTALLED_APPS設置中另外包含 rest_framework.authtoken

INSTALLED_APPS = (
    ...
    'rest_framework.authtoken'
)

 注意: rest_framework.authtoken應用必定要放到INSTALLED_APPS,而且確保在更改設置後運行python manage.py migrate。 rest_framework.authtoken應用須要建立一張表用來存儲用戶與Token的對應關係。

數據庫遷移完成後,能夠看到多了一個authtoken_token表,表結構以下

mysql> show create table authtoken_token\G
*************************** 1. row ***************************
       Table: authtoken_token
Create Table: CREATE TABLE `authtoken_token` (
  `key` varchar(40) NOT NULL,
  `created` datetime(6) NOT NULL,
  `user_id` int(11) NOT NULL,
  PRIMARY KEY (`key`),
  UNIQUE KEY `user_id` (`user_id`),
  CONSTRAINT `authtoken_token_user_id_35299eff_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

  其中「user_id」字段關聯到了用戶表。

  • 配置URLconf

  使用TokenAuthentication時,你可能但願爲客戶提供一種機制,以獲取給定用戶名和密碼的令牌。 REST framework 提供了一個內置的視圖來支持這種行爲。要使用它,請將obtain_auth_token視圖添加到您的 URLconf 中:

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

  其中,r'^api-token-auth/'部分實際上能夠用任何你想使用URL替代。

  • 建立Token

你還須要爲用戶建立令牌,用戶令牌與用戶是一一對應的。若是你已經建立了一些用戶,則能夠爲全部現有用戶生成令牌,例如:

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)

你也能夠爲某個已經存在的用戶建立Token:

for user in User.objects.filter(username='admin'):

    Token.objects.get_or_create(user=user)

建立成功後,會在Token表中生成對應的Token信息。

若是你但願每一個用戶都擁有一個自動生成的令牌,則只需捕捉用戶的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 啓動時將導入的其餘某個位置。

  • 獲取Token

上面雖然介紹了多種建立Token的方式,其實咱們最簡單的就是隻須要配置一下urls.py,而後就能夠經過暴露的API來獲取Token了。當使用表單數據或 JSON 將有效的usernamepassword字段發佈到視圖時,obtain_auth_token視圖將返回 JSON 響應:

$ curl -d "username=admin&password=admin123456" http://127.0.0.1:8000/api-token-auth/
{"token":"684b41712e8e38549504776613bd5612ba997616"}

請注意,缺省的obtain_auth_token視圖顯式使用 JSON 請求和響應,而不是使用你設置的默認的渲染器和解析器類。

當咱們正常獲取到Token後,obtain_auth_token視圖會自動幫咱們在Token表中建立對應的Token。源碼以下:

class ObtainAuthToken(APIView):
    throttle_classes = ()
    permission_classes = ()
    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
    renderer_classes = (renderers.JSONRenderer,)
    serializer_class = AuthTokenSerializer
    if coreapi is not None and coreschema is not None:
        schema = ManualSchema(
            fields=[
                coreapi.Field(
                    name="username",
                    required=True,
                    location='form',
                    schema=coreschema.String(
                        title="Username",
                        description="Valid username for authentication",
                    ),
                ),
                coreapi.Field(
                    name="password",
                    required=True,
                    location='form',
                    schema=coreschema.String(
                        title="Password",
                        description="Valid password for authentication",
                    ),
                ),
            ],
            encoding="application/json",
        )
 
    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})
 
 
obtain_auth_token = ObtainAuthToken.as_view()

默認狀況下,沒有權限或限制應用於obtain_auth_token視圖。 若是您但願應用throttling,則須要重寫視圖類,並使用throttle_classes屬性包含它們。

若是你須要自定義obtain_auth_token視圖,你能夠經過繼承ObtainAuthToken視圖類來實現,並在你的urls.py中使用它。例如,你可能會返回超出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())
]
  • 認證Token

當咱們獲取到Token後,就能夠拿着這個Token來認證其餘API了。對於客戶端進行身份驗證,令牌密鑰應包含在 Authorization HTTP header 中。關鍵字應以字符串文字 「Token」 爲前綴,用空格分隔兩個字符串。例如:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

注意: 若是你想在 header 中使用不一樣的關鍵字(例如Bearer),只需子類化TokenAuthentication並設置keyword類變量。

若是成功經過身份驗證,TokenAuthentication將提供如下憑據。

  • request.user是一個User實例,包含了用戶名及相關信息。
  • request.auth是一個rest_framework.authtoken.models.Token實例。

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

WWW-Authenticate: Token

測試令牌認證的API,例如:

$ curl -X GET -H 'Authorization: Token 684b41712e8e38549504776613bd5612ba997616' http://127.0.0.1:8000/virtual/
注意: 若是您在生產中使用TokenAuthentication,則必須確保您的 API 只能經過https訪問。
PS:DRF自帶的TokenAuthentication認證方式也很是簡單,同時弊端也很大,真正項目中用的較少。因爲須要存儲在數據庫表中,它在分佈式系統中用起來較爲麻煩,而且每次都須要查詢數據庫,增長數據庫壓力;同時它不支持Token的過時設置,這是一個很大的問題。在實際先後端分離項目中使用JWT(Json Web Token)標準的認證方式較多,每一個語言都有各自實現JWT的方式,Python也不例外。

 參考:http://www.ywnds.com/?p=14967

相關文章
相關標籤/搜索