
如何快速實(shí)現(xiàn)REST API集成以優(yōu)化業(yè)務(wù)流程
仍然在api/settings.py
文件中,您需要添加一個(gè)包含JWT設(shè)置的字典。這個(gè)字典有助于定義JWT使用的默認(rèn)值。在繼續(xù)之前,請(qǐng)將以下代碼復(fù)制并粘貼到您的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,
}
# ...
**此時(shí),您已經(jīng)在項(xiàng)目中成功配置了JWT,并且也設(shè)置了全局的身份驗(yàn)證參數(shù)。這些身份驗(yàn)證設(shè)置會(huì)默認(rèn)應(yīng)用于API中的所有視圖,除非您在視圖級(jí)別進(jìn)行了特定的覆蓋。
接下來,讓我們深入探討一下DRF中的權(quán)限設(shè)置。
權(quán)限、身份驗(yàn)證和限制共同決定了是否應(yīng)該授予某個(gè)請(qǐng)求訪問權(quán)限或拒絕其訪問。
權(quán)限檢查總是在視圖處理流程的最開始階段進(jìn)行,只有在權(quán)限檢查通過之后,才會(huì)允許其他代碼繼續(xù)執(zhí)行。權(quán)限檢查通常會(huì)利用request.user
和request.auth
屬性中的身份驗(yàn)證信息,來判斷是否應(yīng)該允許傳入的請(qǐng)求。
權(quán)限機(jī)制用于為不同類別的用戶授予或拒絕訪問API不同部分的權(quán)限。
最簡(jiǎn)單的權(quán)限設(shè)置是允許所有經(jīng)過身份驗(yàn)證的用戶訪問,而拒絕所有未經(jīng)身份驗(yàn)證的用戶。在DRF中,這對(duì)應(yīng)于IsAuthenticated
類。
現(xiàn)在讓我們?yōu)锳PI添加權(quán)限。打開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 的權(quán)限系統(tǒng),還特地為未經(jīng)身份驗(yàn)證的用戶提供了對(duì) API 的只讀訪問權(quán)限。至此,您已經(jīng)成功配置了全局性的權(quán)限設(shè)置,這些設(shè)置將默認(rèn)應(yīng)用于 API 中與特定端點(diǎn)(例如 songs/:id
)相關(guān)聯(lián)的詳情視圖。當(dāng)然,您也有權(quán)在視圖層面對(duì)全局設(shè)置進(jìn)行個(gè)性化調(diào)整。在接下來的內(nèi)容中,當(dāng)我們?cè)?GET songs/
端點(diǎn)上配置權(quán)限時(shí),您將親眼見證這一過程的實(shí)現(xiàn)。
為了讓用戶能夠順利使用您的 API,他們需要先成功登錄并獲得相應(yīng)的令牌。現(xiàn)在,您需要添加一個(gè)視圖,專門用于處理用戶嘗試登錄 API 時(shí)的身份驗(yàn)證流程,并向客戶端返回令牌。
在正式開始編碼視圖之前,按照慣例,我們先添加相應(yīng)的測(cè)試。
請(qǐng)打開 music/tests.py
文件,并添加以下代碼行。同時(shí),您還需要對(duì) BaseViewTest
類進(jìn)行更新,具體更新內(nèi)容如下面的代碼片段所示。
# ...
# 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")
然后添加擴(kuò)展新的AuthLoginUserTest
的BaseViewTest
,如下面的代碼片段所示;
# ....
# 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)
由于您的登錄視圖需要向客戶端返回令牌,因此您需要定義一個(gè)序列化器,用于將令牌數(shù)據(jù)序列化為客戶端可以理解的格式。在第一部分中,我已經(jīng)解釋了為什么對(duì)于返回?cái)?shù)據(jù)的視圖,我們需要為其指定序列化器。
現(xiàn)在,為了創(chuàng)建一個(gè)用于序列化令牌的序列化器,請(qǐng)打開 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 允許我們?cè)谝晥D級(jí)別自定義權(quán)限類,以覆蓋全局設(shè)置。在上面的代碼示例中,我們通過設(shè)置? LoginView
?的?permission_classes
?屬性,實(shí)現(xiàn)了對(duì)登錄視圖的權(quán)限控制,這里我們使用了?AllowAny
?類,它允許所有人公開訪問這個(gè)視圖,因?yàn)樽鳛橐粋€(gè)登錄接口,其公開性是至關(guān)重要的。
接下來,我們需要為第一部分中定義的 ListSongsView
設(shè)置權(quán)限。由于我們希望保護(hù)歌曲列表,防止未登錄的用戶查看,因此我們將 ListSongsView
的 permission_classes
屬性設(shè)置為 IsAuthenticated
。經(jīng)過這樣的設(shè)置后,ListSongsView
的代碼應(yīng)該如下所示:
class ListSongsView(ListAPIView):
"""
Provides a get method handler.
"""
queryset = Songs.objects.all()
serializer_class = SongsSerializer
permission_classes = (permissions.IsAuthenticated,)
在正式運(yùn)行測(cè)試之前,我們還需要將 LoginView
連接到對(duì)應(yīng)的URL上。這樣,當(dāng)用戶嘗試訪問登錄接口時(shí),Django才能正確地調(diào)用 LoginView
來處理請(qǐng)求。
打開music/urls.py
文件并將以下代碼行添加到現(xiàn)有的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")
# ...
]
首先,讓我們運(yùn)行自動(dòng)化測(cè)試。運(yùn)行命令;
(venv)music_service$ python manage.py test
執(zhí)行完畢后,您將在終端中看到測(cè)試結(jié)果輸出;
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'...
遇到測(cè)試失敗時(shí),請(qǐng)不必驚慌!通常,問題的根源往往隱藏在一些細(xì)微之處。
這實(shí)際上是一個(gè)積極的信號(hào),意味著我們之前在第1部分中編寫的測(cè)試現(xiàn)在因?yàn)橄?code>ListSongsView添加了權(quán)限而失敗了。
在命令行界面中,測(cè)試失敗的反饋可能不夠直觀,難以迅速定位問題所在。為了更清晰地了解失敗的原因,你可以嘗試在瀏覽器中訪問http://127.0.0.1:8000/api/v1/songs/
。此時(shí),你應(yīng)該會(huì)看到類似下面的提示信息:
當(dāng)你看到紅色的錯(cuò)誤信息提示“未提供身份驗(yàn)證憑證”時(shí),這意味著你現(xiàn)在需要登錄才能查看所有歌曲。這正是我們?cè)诒窘坛踢@一部分想要實(shí)現(xiàn)的目標(biāo):保護(hù)API端點(diǎn),確保只有經(jīng)過身份驗(yàn)證的用戶才能訪問。
現(xiàn)在,要修復(fù)第 1 部分中的測(cè)試,請(qǐng)按照下面代碼片段中的注釋進(jìn)行操作,并按照說明進(jìn)行操作。
# 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)
在成功更新測(cè)試代碼后,您的測(cè)試現(xiàn)在應(yīng)該已經(jīng)全部通過。
既然您已經(jīng)有了用戶登錄的視圖,那么我認(rèn)為您同樣需要一個(gè)用于用戶注冊(cè)的視圖。作為額外獎(jiǎng)勵(lì),我將向您展示如何添加一個(gè)供API用戶使用的注冊(cè)視圖。
此視圖將對(duì)所有人開放,因此您無需對(duì)其設(shè)置訪問限制。您可以將視圖的permission_classes
設(shè)置為AllowAny
,以便任何人都能訪問。
首先您需要編寫測(cè)試。
打開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)
現(xiàn)在打開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)
在正式運(yùn)行測(cè)試之前,我們還需要做一項(xiàng)重要的工作:通過配置URL來將新視圖鏈接起來。
打開music/urls.py
文件并將以下代碼行添加到現(xiàn)有的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")
# ...
]
現(xiàn)在,URL已經(jīng)配置完畢,您可以使用python manage.py test
命令來運(yùn)行測(cè)試了。
如您所見,在Django REST Framework(DRF)中設(shè)置安全性是非常簡(jiǎn)單的。您只需編寫極少量的代碼即可完成。
您通過全局設(shè)置來配置API的授權(quán)和權(quán)限,從而確保API的安全性。
此外,您還可以在每個(gè)視圖級(jí)別上設(shè)置安全性。
為了練習(xí),您可以繼續(xù)實(shí)現(xiàn)下面這個(gè)表格中列出的簡(jiǎn)單音樂服務(wù)API的其余認(rèn)證端點(diǎn);
1 | Endpoint | HTTP Method | CRUD Method | Response |
2 | auth/login/ | POST | READ | Authenticate user |
3 | auth/register/ | POST | CREATE | Create a new user |
4 | auth/reset-password/ | PUT | UPDATE | Update user password |
5 | auth/logout/ | GET | READ | Logout user |
我強(qiáng)烈推薦您瀏覽以下資源,以便更深入地了解其他HTTP身份驗(yàn)證方案。掌握這些方案并洞悉安全性的演進(jìn)歷程至關(guān)重要。
此外,我還推薦您使用一款名為Postman的出色工具,它能幫助您手動(dòng)測(cè)試API端點(diǎn)。您只需在開發(fā)電腦上進(jìn)行安裝,即可立即上手嘗試。
希望以上內(nèi)容對(duì)您有所幫助,并衷心感謝您閱讀本文。
原文鏈接:https://medium.com/backticks-tildes/lets-build-an-api-with-django-rest-framework-part-2-cfb87e2c8a6c
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)