教程4:認(rèn)證和權(quán)限
目前我們的API對(duì)誰(shuí)可以編輯或刪除代碼片段沒有任何限制。我們希望有一些更高級(jí)的行為,以確保:
- 代碼片段始終與創(chuàng)建者相關(guān)聯(lián)。
- 只有經(jīng)過(guò)身份驗(yàn)證的用戶才可以創(chuàng)建片段。
- 只有代碼段的創(chuàng)建者可能會(huì)更新或刪除它。
- 未經(jīng)身份驗(yàn)證的請(qǐng)求應(yīng)具有完全只讀訪問權(quán)限。
將信息添加到我們的模型
我們將對(duì)Snippet模型類進(jìn)行一些更改。首先,我們添加幾個(gè)字段。其中一個(gè)字段將用于表示創(chuàng)建代碼段的用戶。其他字段將用于存儲(chǔ)代碼的突出顯示的HTML表示形式。
將以下兩個(gè)字段添加到Snippet模型中models.py。
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
我們還需要確保保存模型時(shí),使用pygments代碼高亮庫(kù)來(lái)填充突出顯示的字段。
我們需要一些額外的導(dǎo)入:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
現(xiàn)在我們可以在.save()模型類中添加一個(gè)方法:
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
完成之后,我們需要更新我們的數(shù)據(jù)庫(kù)表。通常我們會(huì)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)遷移來(lái)完成這個(gè)任務(wù),但是為了本教程的目的,我們只需刪除數(shù)據(jù)庫(kù)并重新啟動(dòng)即可。
rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
您可能還想創(chuàng)建幾個(gè)不同的用戶,以用于測(cè)試API。最快的方法就是使用createsuperuser命令。
python manage.py createsuperuser
為我們的用戶模型添加端點(diǎn)
現(xiàn)在我們有一些用戶可以使用,我們最好將這些用戶的表示添加到我們的API中。創(chuàng)建一個(gè)新的序列化器很容易。在serializers.py添加:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
因?yàn)樵赨ser模型上'snippets'是一個(gè)反向關(guān)系,所以在使用這個(gè)ModelSerializer類的時(shí)候默認(rèn)不會(huì)包含它,所以我們需要為它添加一個(gè)顯式的字段。
我們也會(huì)添加幾行代碼到views.py中。我們希望只使用只讀視圖為用戶表示,所以我們將使用ListAPIView和RetrieveAPIView通用的基于類的意見。
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
確保也要導(dǎo)入這個(gè)UserSerializer類
from snippets.serializers import UserSerializer
最后,我們需要將這些視圖添加到API中,方法是從URL conf中引用它們。將以下內(nèi)容添加到中的模式中urls.py。
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
將片段與用戶相關(guān)聯(lián)
現(xiàn)在,如果我們創(chuàng)建了一個(gè)代碼片段,那么就沒有辦法將創(chuàng)建片段的用戶與代碼片段實(shí)例關(guān)聯(lián)起來(lái)。用戶不是作為序列化表示的一部分發(fā)送的,而是作為傳入請(qǐng)求的屬性。
我們處理這個(gè)問題的方式是通過(guò)覆蓋.perform_create()片段視圖上的方法,這允許我們修改實(shí)例保存的管理方式,并處理傳入請(qǐng)求或請(qǐng)求URL中隱含的任何信息。
在SnippetList視圖類中,添加以下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
create()我們的序列化程序的方法現(xiàn)在將被傳遞一個(gè)額外的'owner'字段,以及來(lái)自請(qǐng)求的驗(yàn)證數(shù)據(jù)。
更新我們的序列化器
現(xiàn)在,片段與創(chuàng)建它們的用戶相關(guān)聯(lián),讓我們更新SnippetSerializer以反映這一點(diǎn)。將以下字段添加到序列化程序定義中serializers.py:
owner = serializers.ReadOnlyField(source='owner.username')
注意:確保你也添加'owner',到內(nèi)部Meta類的字段列表中。
這個(gè)字段正在做一些有趣的事情。source參數(shù)控制用于填充的字段,并且可以在對(duì)串行化實(shí)例的任何屬性點(diǎn)。它也可以采用上面顯示的虛線符號(hào),在這種情況下,它將以與Django的模板語(yǔ)言類似的方式遍歷給定的屬性。
我們添加的字段是無(wú)類型的ReadOnlyField類,與其他類型的字段相比,例如CharField,BooleanField等等... untyped ReadOnlyField總是只讀的,并且將被用于序列化的表示,但不會(huì)被用于更新模型當(dāng)它們被反序列化時(shí)。我們也可以CharField(read_only=True)在這里使用。
將所需的權(quán)限添加到視圖
現(xiàn)在,代碼片段與用戶相關(guān)聯(lián),我們要確保只有經(jīng)過(guò)身份驗(yàn)證的用戶才能創(chuàng)建,更新和刪除代碼片段。
REST框架包含許多權(quán)限類,我們可以使用這些權(quán)限類來(lái)限制可以訪問給定視圖的人員。在這種情況下,我們正在尋找的是IsAuthenticatedOrReadOnly確保通過(guò)身份驗(yàn)證的請(qǐng)求獲得讀寫訪問權(quán)限,未經(jīng)身份驗(yàn)證的請(qǐng)求獲得只讀訪問權(quán)限。
首先在視圖模塊中添加以下導(dǎo)入
from rest_framework import permissions
接著,下面的屬性添加到都在SnippetList和SnippetDetail視圖類。
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
添加登錄到Browsable API
如果您現(xiàn)在打開瀏覽器并導(dǎo)航到可瀏覽的API,則會(huì)發(fā)現(xiàn)您不再能夠創(chuàng)建新的代碼片段。為了做到這一點(diǎn),我們需要能夠以用戶身份登錄。
我們可以通過(guò)編輯我們的項(xiàng)目級(jí)urls.py文件中的URLconf來(lái)添加可瀏覽API的登錄視圖。
在文件頂部添加以下導(dǎo)入:
from django.conf.urls import include
然后,在文件末尾添加一個(gè)模式,以包含可瀏覽API的登錄和注銷視圖。
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls')),
]
r'^api-auth/'模式的一部分實(shí)際上可以是任何您想要使用的URL。
現(xiàn)在,如果您再次打開瀏覽器并刷新頁(yè)面,則會(huì)在頁(yè)面右上方看到一個(gè)“登錄”鏈接。如果您以前創(chuàng)建的用戶之一登錄,則可以再次創(chuàng)建代碼片段。
一旦創(chuàng)建了一些代碼片段,請(qǐng)導(dǎo)航到“/ users /”端點(diǎn),并注意到代表性包括每個(gè)用戶的“代碼段”字段中與每個(gè)用戶關(guān)聯(lián)的代碼段ID列表。
對(duì)象級(jí)權(quán)限
實(shí)際上,我們希望所有人都可以看到所有代碼片段,但也要確保只有創(chuàng)建代碼段的用戶才能更新或刪除它。
要做到這一點(diǎn),我們將需要?jiǎng)?chuàng)建一個(gè)自定義權(quán)限。
在snippets應(yīng)用程序中,創(chuàng)建一個(gè)新文件, permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
現(xiàn)在,我們可以通過(guò)編輯視圖類的permission_classes屬性,將自定義權(quán)限添加到代碼片段實(shí)例端點(diǎn)SnippetDetail:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
確保也要導(dǎo)入這個(gè)IsOwnerOrReadOnly類。
from snippets.permissions import IsOwnerOrReadOnly
現(xiàn)在,如果您再次打開瀏覽器,則如果您以創(chuàng)建代碼段的相同用戶身份登錄,則會(huì)發(fā)現(xiàn)“DELETE”和“PUT”操作僅出現(xiàn)在代碼片段實(shí)例端點(diǎn)上。
使用API??進(jìn)行身份驗(yàn)證
因?yàn)槲覀儸F(xiàn)在在API上擁有一組權(quán)限,所以如果我們要編輯任何代碼片段,我們需要驗(yàn)證我們的請(qǐng)求。我們還沒有設(shè)置任何認(rèn)證類,所以默認(rèn)是當(dāng)前應(yīng)用的,這是SessionAuthentication和BasicAuthentication。
當(dāng)我們通過(guò)Web瀏覽器與API進(jìn)行交互時(shí),我們可以登錄,然后瀏覽器會(huì)話將為請(qǐng)求提供所需的身份驗(yàn)證。
如果我們正在以編程方式與API進(jìn)行交互,則需要在每個(gè)請(qǐng)求上明確提供身份驗(yàn)證憑據(jù)。
如果我們嘗試創(chuàng)建一個(gè)沒有驗(yàn)證的片段,我們會(huì)得到一個(gè)錯(cuò)誤:
http POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"detail": "Authentication credentials were not provided."
}
我們可以通過(guò)包含我們之前創(chuàng)建的用戶的用戶名和密碼來(lái)提出成功的請(qǐng)求。
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
{
"id": 1,
"owner": "admin",
"title": "foo",
"code": "print 789",
"linenos": false,
"language": "python",
"style": "friendly"
}
概要
我們現(xiàn)在已經(jīng)在我們的Web API上獲得了相當(dāng)細(xì)致的權(quán)限集合,以及系統(tǒng)用戶和他們創(chuàng)建的代碼片段的端點(diǎn)。
在本教程的第5部分中,我們將研究如何通過(guò)為突出顯示的片段創(chuàng)建HTML端點(diǎn)來(lái)將所有內(nèi)容綁定在一起,并通過(guò)使用系統(tǒng)內(nèi)關(guān)系的超鏈接來(lái)改進(jìn)API的凝聚力。