# Look for the REST_FRAMEWORK inside the settings.py file

REST_FRAMEWORK = {
# ...

# inside the Rest framework settings dictionary, add the auth settings
# Authentication settings
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
],

#...
}

仍然在api/settings.py文件中,您需要添加一個包含JWT設置的字典。這個字典有助于定義JWT使用的默認值。在繼續之前,請將以下代碼復制并粘貼到您的api/settings.py文件中。

# ...

# Inside the settings.py file, add the JWT dictionary

# JWT settings
JWT_AUTH = {
'JWT_ENCODE_HANDLER':
'rest_framework_jwt.utils.jwt_encode_handler',

'JWT_DECODE_HANDLER':
'rest_framework_jwt.utils.jwt_decode_handler',

'JWT_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_payload_handler',

'JWT_PAYLOAD_GET_USER_ID_HANDLER':
'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler',

'JWT_RESPONSE_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_response_payload_handler',

'JWT_SECRET_KEY': SECRET_KEY,
'JWT_GET_USER_SECRET_KEY': None,
'JWT_PUBLIC_KEY': None,
'JWT_PRIVATE_KEY': None,
'JWT_ALGORITHM': 'HS256',
'JWT_VERIFY': True,
'JWT_VERIFY_EXPIRATION': True,
'JWT_LEEWAY': 0,
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
'JWT_AUDIENCE': None,
'JWT_ISSUER': None,

'JWT_ALLOW_REFRESH': False,
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),

'JWT_AUTH_HEADER_PREFIX': 'Bearer',
'JWT_AUTH_COOKIE': None,
}

# ...

**此時,您已經在項目中成功配置了JWT,并且也設置了全局的身份驗證參數。這些身份驗證設置會默認應用于API中的所有視圖,除非您在視圖級別進行了特定的覆蓋。

接下來,讓我們深入探討一下DRF中的權限設置。

為什么我們需要設置權限呢?

權限、身份驗證和限制共同決定了是否應該授予某個請求訪問權限或拒絕其訪問。

權限檢查總是在視圖處理流程的最開始階段進行,只有在權限檢查通過之后,才會允許其他代碼繼續執行。權限檢查通常會利用request.userrequest.auth屬性中的身份驗證信息,來判斷是否應該允許傳入的請求。

權限機制用于為不同類別的用戶授予或拒絕訪問API不同部分的權限。

最簡單的權限設置是允許所有經過身份驗證的用戶訪問,而拒絕所有未經身份驗證的用戶。在DRF中,這對應于IsAuthenticated類。

現在讓我們為API添加權限。打開api/settings.py文件并將DjangoPermissionsOrnonReadOnly添加到Django REST框架的DEFAULT_PERMISSION_CLASSES中。

# ...

# Look for the REST_FRAMEWORK inside the settings.py file

REST_FRAMEWORK = {
# ...

# inside the Rest framework settings dictionary, add the permission settings
# Permission settings
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],

#...
}

DjangoModelPermissionsOrAnonReadOnly 類不僅融合了 Django 的權限系統,還特地為未經身份驗證的用戶提供了對 API 的只讀訪問權限。至此,您已經成功配置了全局性的權限設置,這些設置將默認應用于 API 中與特定端點(例如 songs/:id)相關聯的詳情視圖。當然,您也有權在視圖層面對全局設置進行個性化調整。在接下來的內容中,當我們在 GET songs/ 端點上配置權限時,您將親眼見證這一過程的實現。

用戶登錄視圖

為了讓用戶能夠順利使用您的 API,他們需要先成功登錄并獲得相應的令牌。現在,您需要添加一個視圖,專門用于處理用戶嘗試登錄 API 時的身份驗證流程,并向客戶端返回令牌。

測試先行

在正式開始編碼視圖之前,按照慣例,我們先添加相應的測試。

請打開 music/tests.py 文件,并添加以下代碼行。同時,您還需要對 BaseViewTest 類進行更新,具體更新內容如下面的代碼片段所示。

# ...

# Add this line at the top of the tests.py file
from django.contrib.auth.models import User

# update the BaseViewTest to this

class BaseViewTest(APITestCase):
client = APIClient()

@staticmethod
def create_song(title="", artist=""):
if title != "" and artist != "":
Songs.objects.create(title=title, artist=artist)

def login_a_user(self, username="", password=""):
url = reverse(
"auth-login",
kwargs={
"version": "v1"
}
)
return self.client.post(
url,
data=json.dumps({
"username": username,
"password": password
}),
content_type="application/json"
)

def setUp(self):
# create a admin user
self.user = User.objects.create_superuser(
username="test_user",
email="test@mail.com",
password="testing",
first_name="test",
last_name="user",
)
# add test data
self.create_song("like glue", "sean paul")
self.create_song("simple song", "konshens")
self.create_song("love is wicked", "brick and lace")
self.create_song("jam rock", "damien marley")

然后添加擴展新的AuthLoginUserTestBaseViewTest,如下面的代碼片段所示;

# ....
# Add this line at the top of your tests.py file
from django.contrib.auth.models import User

# ...

# Then add these lines of code at the end of your tests.py file

class AuthLoginUserTest(BaseViewTest):
"""
Tests for the auth/login/ endpoint
"""

def test_login_user_with_valid_credentials(self):
# test login with valid credentials
response = self.login_a_user("test_user", "testing")
# assert token key exists
self.assertIn("token", response.data)
# assert status code is 200 OK
self.assertEqual(response.status_code, status.HTTP_200_OK)
# test login with invalid credentials
response = self.login_a_user("anonymous", "pass")
# assert status code is 401 UNAUTHORIZED
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

創建登錄視圖

由于您的登錄視圖需要向客戶端返回令牌,因此您需要定義一個序列化器,用于將令牌數據序列化為客戶端可以理解的格式。在第一部分中,我已經解釋了為什么對于返回數據的視圖,我們需要為其指定序列化器。

現在,為了創建一個用于序列化令牌的序列化器,請打開 music/serializers.py 文件,并在其中添加以下代碼行。

# Add these lines at the top of your views.py file
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login
from rest_framework_jwt.settings import api_settings
from rest_framework import permissions

# Get the JWT settings, add these lines after the import/from lines
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

# ...

# Add this view to your views.py file

class LoginView(generics.CreateAPIView):
"""
POST auth/login/
"""
# This permission class will overide the global permission
# class setting
permission_classes = (permissions.AllowAny,)

queryset = User.objects.all()

def post(self, request, *args, **kwargs):
username = request.data.get("username", "")
password = request.data.get("password", "")
user = authenticate(request, username=username, password=password)
if user is not None:
# login saves the user’s ID in the session,
# using Django’s session framework.
login(request, user)
serializer = TokenSerializer(data={
# using drf jwt utility functions to generate a token
"token": jwt_encode_handler(
jwt_payload_handler(user)
)})
serializer.is_valid()
return Response(serializer.data)
return Response(status=status.HTTP_401_UNAUTHORIZED)

正如之前所提到的,Django REST Framework 允許我們在視圖級別自定義權限類,以覆蓋全局設置。在上面的代碼示例中,我們通過設置? LoginView?的?permission_classes?屬性,實現了對登錄視圖的權限控制,這里我們使用了?AllowAny?類,它允許所有人公開訪問這個視圖,因為作為一個登錄接口,其公開性是至關重要的。

接下來,我們需要為第一部分中定義的 ListSongsView 設置權限。由于我們希望保護歌曲列表,防止未登錄的用戶查看,因此我們將 ListSongsView 的 permission_classes 屬性設置為 IsAuthenticated。經過這樣的設置后,ListSongsView 的代碼應該如下所示:

class ListSongsView(ListAPIView):
"""
Provides a get method handler.
"""
queryset = Songs.objects.all()
serializer_class = SongsSerializer
permission_classes = (permissions.IsAuthenticated,)

連接視圖

在正式運行測試之前,我們還需要將 LoginView 連接到對應的URL上。這樣,當用戶嘗試訪問登錄接口時,Django才能正確地調用 LoginView 來處理請求。

打開music/urls.py文件并將以下代碼行添加到現有的urlpatterns列表中。

# Add *LoginView* to this line in  your urls.py file
from .views import ListCreateSongsView, SongsDetailView, LoginView

urlpatterns = [
# ...

# Some where in your existing urlpatterns list, Add this line
path('auth/login/', LoginView.as_view(), name="auth-login")

# ...
]

現在讓我們測試一下!

首先,讓我們運行自動化測試。運行命令;

(venv)music_service$ python manage.py test

執行完畢后,您將在終端中看到測試結果輸出;

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.F
===================================================================
FAIL: test_get_all_songs (music.tests.GetAllSongsTest)
-------------------------------------------------------------------
Traceback (most recent call last):
File "/your/dir/to/music_service/music/tests.py", line xxx, in test_get_all_songs
self.assertEqual(response.data, serialized.data)
AssertionError: {'detail': 'Authentication credentials we[13 chars]ed.'} != [OrderedDict([('title', 'like glue'), ('a[224 chars]')])]
--------------------------------------------------------------------Ran 2 tests in 0.010s
FAILED (failures=1)
Destroying test database for alias 'default'...

為什么測試失敗?

遇到測試失敗時,請不必驚慌!通常,問題的根源往往隱藏在一些細微之處。

這實際上是一個積極的信號,意味著我們之前在第1部分中編寫的測試現在因為向ListSongsView添加了權限而失敗了。

在命令行界面中,測試失敗的反饋可能不夠直觀,難以迅速定位問題所在。為了更清晰地了解失敗的原因,你可以嘗試在瀏覽器中訪問http://127.0.0.1:8000/api/v1/songs/。此時,你應該會看到類似下面的提示信息:

當你看到紅色的錯誤信息提示“未提供身份驗證憑證”時,這意味著你現在需要登錄才能查看所有歌曲。這正是我們在本教程這一部分想要實現的目標:保護API端點,確保只有經過身份驗證的用戶才能訪問。

讓我們修復測試

現在,要修復第 1 部分中的測試,請按照下面代碼片段中的注釋進行操作,并按照說明進行操作。

# 1. update the urlpatterns list in api/urls.py file to this;
from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
path('admin/', admin.site.urls),
path('api-token-auth/', obtain_jwt_token, name='create-token'),
re_path('api/(?P<version>(v1|v2))/', include('music.urls'))
]

#2. Then update the BaseViewTest class in music/tests.py and add
# this method to it

# class BaseViewTest(APITestCase):
# ...
def login_client(self, username="", password=""):
# get a token from DRF
response = self.client.post(
reverse('create-token'),
data=json.dumps(
{
'username': username,
'password': password
}
),
content_type='application/json'
)
self.token = response.data['token']
# set the token in the header
self.client.credentials(
HTTP_AUTHORIZATION='Bearer ' + self.token
)
self.client.login(username=username, password=password)
return self.token

#3. Update the test and add invoke self.login_client method
# Below is the complete updated test class from part 1

class GetAllSongsTest(BaseViewTest):

def test_get_all_songs(self):
"""
This test ensures that all songs added in the setUp method
exist when we make a GET request to the songs/ endpoint
"""
# this is the update you need to add to the test, login
self.login_client('test_user', 'testing')
# hit the API endpoint
response = self.client.get(
reverse("songs-all", kwargs={"version": "v1"})
)
# fetch the data from db
expected = Songs.objects.all()
serialized = SongsSerializer(expected, many=True)
self.assertEqual(response.data, serialized.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)

在成功更新測試代碼后,您的測試現在應該已經全部通過。

額外獎勵:用戶注冊視圖

既然您已經有了用戶登錄的視圖,那么我認為您同樣需要一個用于用戶注冊的視圖。作為額外獎勵,我將向您展示如何添加一個供API用戶使用的注冊視圖。

此視圖將對所有人開放,因此您無需對其設置訪問限制。您可以將視圖的permission_classes設置為AllowAny,以便任何人都能訪問。

添加測試

首先您需要編寫測試。

打開music/tests.py并添加以下代碼行;

# ...

# At the end of tests.py file, add these lines of code

class AuthRegisterUserTest(AuthBaseTest):
"""
Tests for auth/register/ endpoint
"""
def test_register_a_user_with_valid_data(self):
url = reverse(
"auth-register",
kwargs={
"version": "v1"
}
)
response = self.client.post(
url,
data=json.dumps(
{
"username": "new_user",
"password": "new_pass",
"email": "new_user@mail.com"
}
),
content_type="application/json"
)
# assert status code is 201 CREATED
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_register_a_user_with_invalid_data(self):
url = reverse(
"auth-register",
kwargs={
"version": "v1"
}
)
response = self.client.post(
url,
data=json.dumps(
{
"username": "",
"password": "",
"email": ""
}
),
content_type='application/json'
)
# assert status code
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

創建注冊視圖

現在打開music/views.py并添加以下代碼行:

# ...

# Add these lines to the views.py file
class RegisterUsers(generics.CreateAPIView):
"""
POST auth/register/
"""
permission_classes = (permissions.AllowAny,)

def post(self, request, *args, **kwargs):
username = request.data.get("username", "")
password = request.data.get("password", "")
email = request.data.get("email", "")
if not username and not password and not email:
return Response(
data={
"message": "username, password and email is required to register a user"
},
status=status.HTTP_400_BAD_REQUEST
)
new_user = User.objects.create_user(
username=username, password=password, email=email
)
return Response(status=status.HTTP_201_CREATED)

連接視圖

在正式運行測試之前,我們還需要做一項重要的工作:通過配置URL來將新視圖鏈接起來。

打開music/urls.py文件并將以下代碼行添加到現有的urlpatterns列表中。

# Add this line to your urls.py file

urlpatterns = [
# ...

# Some where in your existing urlpatterns list, Add this line
path('auth/register/', RegisterUsersView.as_view(), name="auth-register")

# ...
]

現在,URL已經配置完畢,您可以使用python manage.py test命令來運行測試了。

收獲

如您所見,在Django REST Framework(DRF)中設置安全性是非常簡單的。您只需編寫極少量的代碼即可完成。

您通過全局設置來配置API的授權和權限,從而確保API的安全性。

此外,您還可以在每個視圖級別上設置安全性。

為了練習,您可以繼續實現下面這個表格中列出的簡單音樂服務API的其余認證端點;

1EndpointHTTP MethodCRUD MethodResponse
2auth/login/POSTREADAuthenticate user
3auth/register/POSTCREATECreate a new user
4auth/reset-password/PUTUPDATEUpdate user password
5auth/logout/GETREADLogout user

額外資源推薦

我強烈推薦您瀏覽以下資源,以便更深入地了解其他HTTP身份驗證方案。掌握這些方案并洞悉安全性的演進歷程至關重要。

此外,我還推薦您使用一款名為Postman的出色工具,它能幫助您手動測試API端點。您只需在開發電腦上進行安裝,即可立即上手嘗試。

希望以上內容對您有所幫助,并衷心感謝您閱讀本文。

原文鏈接:https://medium.com/backticks-tildes/lets-build-an-api-with-django-rest-framework-part-2-cfb87e2c8a6c

上一篇:

使用Django REST Framework構建API

下一篇:

NestJ框架下的RESTful API簡介
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費