JWT使用方式
關於jwt的三個部分,我這里不介紹了,我們看看JWT的使用方式:
-
首先,前端通過Web表單將自己的用戶名和密碼發送到后端的接口。這一過程一般是一個HTTP POST請求。建議的方式是通過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。
-
后端核對用戶名和密碼成功后,將用戶的id等其他信息作為JWT Payload(負載),將其與頭部分別進行Base64編碼拼接后簽名,形成一個JWT。形成的JWT就是一個形同lll.zzz.xxx的字符串。
-
后端將JWT字符串作為登錄成功的返回結果返回給前端。前端可以將返回的結果保存在localStorage或sessionStorage上,退出登錄時前端刪除保存的JWT即可。
-
前端在每次請求時將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)
- 修改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的安全性