踩坑drf中的jwt


 

JWT使用方式

關於jwt的三個部分,我這里不介紹了,我們看看JWT的使用方式:

  1. 首先,前端通過Web表單將自己的用戶名和密碼發送到后端的接口。這一過程一般是一個HTTP POST請求。建議的方式是通過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。

  2. 后端核對用戶名和密碼成功后,將用戶的id等其他信息作為JWT Payload(負載),將其與頭部分別進行Base64編碼拼接后簽名,形成一個JWT。形成的JWT就是一個形同lll.zzz.xxx的字符串。

  3. 后端將JWT字符串作為登錄成功的返回結果返回給前端。前端可以將返回的結果保存在localStorage或sessionStorage上,退出登錄時前端刪除保存的JWT即可。

  4. 前端在每次請求時將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',
)
其他設置
你可以覆蓋一些其他設置,比如變更Token過期時間,以下是所有可用設置的默認值。在settings.py文件中設置。
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的密鑰,確保這是安全的,不共享不公開的
    'JWT_SECRET_KEY': settings.SECRET_KEY,
    'JWT_GET_USER_SECRET_KEY': None,
    'JWT_PUBLIC_KEY': None,
    'JWT_PRIVATE_KEY': None,
    'JWT_ALGORITHM': 'HS256',
    // 如果秘鑰是錯誤的,它會引發一個jwt.DecodeError
    'JWT_VERIFY': True,
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_LEEWAY': 0,
    // Token過期時間設置
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
    'JWT_AUDIENCE': None,
    'JWT_ISSUER': None,

    // 是否開啟允許Token刷新服務,及限制Token刷新間隔時間,從原始Token獲取開始計算
    'JWT_ALLOW_REFRESH': False,
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),

    // 定義與令牌一起發送的Authorization標頭值前綴
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
    'JWT_AUTH_COOKIE': None,
}

 

快速開始

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請求,響應內容')

注意一個事,配置驗證,權限類的格式:authentication_classes = (驗證類,驗證類, ),要不然就會報這個錯誤:TypeError: 'xxx' object is not iterable。

上面代碼中,通過驗證則代表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請求,響應內容')

 如何測試接口:在請求頭里面加上Authorization字段,值為JWT+ 空格 + token

 最后介紹一下另一種情況:需要配合身份驗證+JWT的方式:請求的時候帶有已驗證的token且沒有過期,就會通過執行對應請求方式,要不就沒有通過,request.user拿到用戶名。

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
from rest_framework.permissions import IsAuthenticated

class IndexViewset(APIView):

   permission_classes = (IsAuthenticated,)
    authentication_classes = (JSONWebTokenAuthentication, authentication.SessionAuthentication,)

    def get(self, request, *args, **kwargs): 
        print(request.user) #獲取請求的用戶    return Response('POST請求,響應內容')
     return Response('GER請求,響應內容')

    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 = (
  #選擇自己的驗證類
  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的字段。

 注意:validate方法中的調用authenticate方法是哪個authenticate?

django.contrib.auth.backends.ModelBackend為django默認的authenticate,只能處理用戶名和密碼。

users.views.CustomBackend為自定義authenticate,可以處理更多參數。

 

JWT存儲在哪里?

JWT在服務端是無狀態的,只需要解密,查看token是否有效,解密出來的信息中含有用戶信息,而客戶端需要保存token,並且當登錄驗證成功以后的請求就應該攜帶token,那么客戶端如何存儲token,客戶端可以通過什么方式傳遞token?

token存儲在哪里:

  • HTML5 Web Storage (localStorage或sessionStorage)
  • Cookies

HTML5 Web Storage (localStorage或sessionStorage)

當你使用ajax post請求登錄成功拿到token,觸發回調函數,存儲token到sessionStorage

function tokenSuccess(err, response) {
    if(err){
        throw err;
    }
    $window.sessionStorage.accessToken = response.body.access_token;
}

再次請求時通過ajax 在請求頭添加Authorization:  Bearer token字符串

JWT Cookie Storage

這種方式就很簡單了,登錄驗證成功,服務端將token設置到cookie中。

客戶端每次請求就會自動帶上set-Cookie,服務端獲取cookie中的token。

 

JWT sessionStorage和localStorage的安全性

Web存儲(localStorage/sessionStorage)可以通過同一域上JavaScript訪問。這意味着任何在你的網站上運行的JavaScript都可以訪問Web存儲,因為這樣容易受到跨站點腳本(XSS)攻擊。
 
JWT Cookie存儲的安全性
Cookies,當使用帶有HttpOnly的cookie標志時,通過JavaScript是無法訪問的,並且對XSS是免疫的。你還可以設置安全的cookie標志來保證cokie僅通過HTTPS發送。但cookies容易受到跨站點請求偽造(CSRF)的攻擊。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM