0X00 安裝及基礎使用
Django JWT是基於Django的auth模塊和user model的,所以如果不使用Django的model那麼是無法使用Django JWT的。其視圖的實現方法是基於Django restframework的APIView和serilizers。
廢話講到這裏,總的來說就是如果你對Django的model和restframework不是很熟,那麼用起來可能有點費力。
1.使用pip安裝
pip install djangorestframework-jwt
2.在你的settings.py
,添加JSONWebTokenAuthentication
到Django REST框架DEFAULT_AUTHENTICATION_CLASSES
SessionAuthentication和BasicAuthentication在使用restframework的調試界面需要用到的模塊。
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
3.urls.py
from django.conf.urls import url
from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token
urlpatterns = [
url(r'^login/', obtain_jwt_token), # 用於獲取token
url(r'^token-verify/', verify_jwt_token), # 驗證令牌是否合法
path(r'api-auth/', include('rest_framework.urls')), # 爲restframework調試頁面也開啓驗證
]
(1)obtain_jwt_token:提交用戶名和密碼,後臺判定是否合法,合法的話返回一個Token,否則認證失敗
- URL:/login/
- Request:
- 請求方法:POST(表單)
- 請求參數:username:'',password:''
Response:
- 200,登錄成功,返回{"token":"xxxxxx"}
- 400,身份驗證未通過
(2)verify_jwt_token:提交一個Token,後臺判定Token是否過期
- URL:/token-verify/
- Request:
- 請求方法:POST(application/json)
- 請求參數:{"token":"xxxx"}
Response:
- 200,驗證通過,Token合法
- 400,Token不合法或已過期
4.攜帶token請求接口
Token需要添加到HTTP請求頭中,見下圖
Key:Authorization
Value:JWT xxxxxxx
0X01 擴展Django User Model並與Django JWT結合
大概率會出現Django默認User Model字段不夠用的情況,那麼就需要繼承User模塊並添加字段。
添加一個APP用於新的User model
python manage.py startapp user_auth
1.models.py
User也是直接繼承於AbsractUser的,所以我們直接繼承該model並添加字段即可。
from django.contrib.auth.models import AbstractUser
from django.db import models
class UserInfo(AbstractUser):
choice = (
(1, 'admin'),
(2, 'user')
)
user_type = models.IntegerField(choices=choice)
2.settings.py
修改Django Auth模塊指向的model,改爲我們重寫後的model
AUTH_USER_MODEL = "user_auth.UserInfo" # 重寫user model後將用戶認證指向重寫後的model
3.保存更改
python manage.py makemigrations
python manage.py migrate
4.使用ModelViewSet和ModelSerializer
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
instance = UserInfo.objects.create_user(**validated_data)
return instance
def update(self, instance, validated_data):
"""
只允許更新password字段
:param instance:
:param validated_data:
:return:
"""
instance.set_password(validated_data['password'])
instance.save()
return instance
class Meta:
model = UserInfo
fields = ('id', 'username', 'user_type', 'date_joined', 'password')
read_only_fields = ['date_joined']
extra_kwargs = {'password': {'write_only': True}}
class UserViewSet(ModelViewSet):
permission_classes = (UserReadAdminWrite,)
queryset = UserInfo.objects.all()
serializer_class = UserSerializer
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if request.user.id == instance.id:
return Response({'ERROR': '不能刪除正在使用的用戶'} ,status=status.HTTP_403_FORBIDDEN)
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
JWT更多設置參數及用法見 http://getblimp.github.io/django-rest-framework-jwt/
0X02
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
# 設置訪問權限爲只讀
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
# 設置訪問權限爲必須登錄
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
# 使用rest_framework的界面需要下列認證模塊
#'rest_framework.authentication.SessionAuthentication',
#'rest_framework.authentication.BasicAuthentication',
),
}
如果加載了sessionAuthentication和BasicAuthentication模塊,那麼通過了JWT身份認證後,會設置一個cookide,session_id=xxx,使用該session_id,不使用Token也可以正常訪問後臺接口,如果只允許Token驗證,需要註釋掉session模塊和basic模塊。
源碼:
Authentication類都必須繼承於BaseAuthentication類:
class BaseAuthentication(object):
"""
All authentication classes should extend BaseAuthentication.
"""
def authenticate(self, request):
"""
Authenticate the request and return a two-tuple of (user, token).
"""
raise NotImplementedError(".authenticate() must be overridden.")
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
pass
定義的Authentication類會在調用視圖函數之前被調用:
class APIView(View):
# The following policies may be set at either globally, or per-view.
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # !!!#
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
# Allow dependency injection of other settings to make testing easier.
settings = api_settings
schema = DefaultSchema()
dispatch方法應該是用於處理所有請求,dispatch函數:
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs) # 該方法尤爲重要,如下
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
def initial(self, request, *args, **kwargs):
"""
運行所有在視圖函數前應該被調用的東西
"""
self.format_kwarg = self.get_format_suffix(**kwargs)
# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
# Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# Ensure that the incoming request is permitted
self.perform_authentication(request) # 用戶認證
self.check_permissions(request) # 權限認證
self.check_throttles(request) # 訪問頻率限制
用戶認證的具體方法:
perform_authentication:該函數只有一個request.user屬性,利用了property的特性,通過屬性轉換爲方法。
def perform_authentication(self, request):
"""
Perform authentication on the incoming request.
Note that if you override this and simply 'pass', then authentication
will instead be performed lazily, the first time either
`request.user` or `request.auth` is accessed.
"""
request.user
調用的具體方法:
request.py的Request類的方法:
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate() # 調用所有認證方法
return self._user
_authenticate方法:遍歷加載的所有authentication方法並認證
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()