踩坑drf中的jwt
JWT使用方式
關於jwt的三個部分,我這裡不介紹了,我們看看JWT的使用方式:
-
首先,前端通過Web表單將自己的使用者名稱和密碼傳送到後端的介面。這一過程一般是一個HTTP POST請求。建議的方式是通過SSL加密的傳輸(https協議),從而避免敏感資訊被嗅探。
-
後端核對使用者名稱和密碼成功後,將使用者的id等其他資訊作為JWT Payload(負載),將其與頭部分別進行Base64編碼拼接後簽名,形成一個JWT。形成的JWT就是一個形同lll.zzz.xxx的字串。
-
後端將JWT字串作為登入成功的返回結果返回給前端。前端可以將返回的結果儲存在localStorage或sessionStorage
-
前端在每次請求時將JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問題)
5. 後端檢查JWT是否存在,及其他,例如,檢查簽名是否正確;檢查Token是否過期;檢查Token的接收方是否是自己(可選),如果簽名驗證通過,我們也可以獲得此JWT所屬的使用者。
Django REST framework 中使用JWT認證
準備工作
1.安裝
pip install djangorestframework-jwt
2.settings.py配置:我這裡沒有配置全域性的許可權與認證,等會在單獨的介面單獨配置;另外配置JWT時效與JWT字串的字首;最後設定驗證類,主要是驗證類中的authenticate方法。
REST_FRAMEWORK = { # 設定所有介面都需要被驗證 'DEFAULT_PERMISSION_CLASSES': ( #’rest_framework.permissions.IsAuthenticatedOrReadOnly’, ), # 使用者登陸認證方式 'DEFAULT_AUTHENTICATION_CLASSES': ( # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', #’rest_framework.authentication.SessionAuthentication’,#’rest_framework.authentication.BasicAuthentication’, ), } # jwt載荷中的有效期設定 JWT_AUTH = { #token 有效期 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=1800), 'JWT_AUTH_HEADER_PREFIX': 'JWT', } AUTHENTICATION_BACKENDS = ( #選擇django自己的驗證類 'django.contrib.auth.backends.ModelBackend', )
快速開始
urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('admin/', admin.site.urls),
#登入成功自動簽發token
url(r'^login/', obtain_jwt_token)
]
這裡面主要是obtain_jwt_token,實踐呼叫ObtainJSONWebToken.as_view(),ObtainJSONWebToken類下面,有如下serializer_class = JSONWebTokenSerializer
而JSONWebTokenSerializer就是最重要的類,後面如果你的登入不只有username,password時,需要傳入更多的引數,需要更改這兩個方法。
- __init__方法序列化前端傳遞的username與password,
- validate方法通過authenticate方法驗證username與password是否存在 你的 user 表裡,通過驗證則簽發token
POSTMAN測試:注意首先確保你的這個使用者已經存在
如何給介面加上JWT驗證
views.py
from rest_framework.views import APIView from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework import authentication from rest_framework.response import Response class IndexViewset(APIView): #單獨給這個介面增加JWT認證 authentication_classes = (JSONWebTokenAuthentication, authentication.SessionAuthentication) def get(self, request, *args, **kwargs): return Response('POST請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容')
上面程式碼中,通過驗證則代表JWT有效,那麼如何獲取到此token所屬於的使用者,看如下程式碼:
from rest_framework.views import APIView from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework import authentication from rest_framework.response import Response from rest_framework_jwt.utils import jwt_decode_handler class IndexViewset(APIView): authentication_classes = (JSONWebTokenAuthentication, authentication.SessionAuthentication) def get(self, request, *args, **kwargs): print("驗證後的token:",bytes.decode(request.auth)) token_user = jwt_decode_handler(bytes.decode(request.auth)) print(token_user['user_id']) return Response('POST請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容')
特殊情況
1.如果你想擴充套件django user欄位,新建UserProfile類,繼承AbstractUser,settings.py裡指定user model,並遷移資料庫,請檢視
2.如果使用者登入中不僅有使用者名稱,密碼,還有驗證碼等,就需要重寫驗證類,新建CustomBackend類,繼承ModelBackend,實現authenticate方法,settings.py裡指定
AUTHENTICATION_BACKENDS = (
#選擇django自己的驗證類
users.views.CustomBackend',
#選擇django自己的驗證類
#'django.contrib.auth.backends.ModelBackend',
)
- 修改\Lib\site-packages\django\contrib\auth\__init__.py,替換authenticate。
def authenticate(request=None, **credentials): """ If the given credentials are valid, return a User object. """ for backend, backend_path in _get_backends(return_tuples=True): try: inspect.getcallargs(backend.authenticate, request, **credentials) except TypeError: args = () credentials.pop('request', None) # Does the backend accept a request keyword argument? try: inspect.getcallargs(backend.authenticate, request=request, **credentials) except TypeError: # Does the backend accept credentials without request? try: inspect.getcallargs(backend.authenticate, **credentials) except TypeError: # This backend doesn't accept these credentials as arguments. Try the next one. return None else: warnings.warn( "Update %s.authenticate() to accept a positional " "`request` argument." % backend_path ) else: credentials['request'] = request warnings.warn( "In %s.authenticate(), move the `request` keyword argument " "to the first positional argument." % backend_path ) # Annotate the user object with the path of the backend. return backend.authenticate(*args, **credentials) # The credentials supplied are invalid to all backends, fire signal user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request)View Code
- 修改ObtainJSONWebToken下的__init__方法與validate。