当前位置:   article > 正文

Django JWT身份验证_django jwtauthentication

django jwtauthentication

0X00 安装及基础使用

Django JWT是基于Django的auth模块和user model的,所以如果不使用Django的model那么是无法使用Django JWT的。其视图的实现方法是基于Django restframework的APIView和serializers。修正一下,准确的来说Django restframework JWT默认是基于auth模块和user model的,如果你对源码足够熟悉,将各个模块重写,那么将JWT结合到你自己的认证系统也是没有任何问题,不过使用自己的认证系统的话完全可以使用Token模块自己生成Token了,这种情况下使用JWT有点多此一举了。

废话讲到这里,正文开始

1.使用pip安装

pip install djangorestframework-jwt

2.在你的settings.py,添加JSONWebTokenAuthentication到Django REST框架DEFAULT_AUTHENTICATION_CLASSES

SessionAuthentication和BasicAuthentication在使用restframework的调试界面需要用到的模块。

  1. REST_FRAMEWORK = {
  2. 'DEFAULT_PERMISSION_CLASSES': (
  3. 'rest_framework.permissions.IsAuthenticated',
  4. ),
  5. 'DEFAULT_AUTHENTICATION_CLASSES': (
  6. 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
  7. 'rest_framework.authentication.SessionAuthentication',
  8. 'rest_framework.authentication.BasicAuthentication',
  9. ),
  10. }

3.urls.py

  1. from django.conf.urls import url
  2. from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token
  3. urlpatterns = [
  4. url(r'^login/', obtain_jwt_token), # 用于获取token
  5. url(r'^token-verify/', verify_jwt_token), # 验证令牌是否合法
  6. path(r'api-auth/', include('rest_framework.urls')), # 为restframework调试页面也开启验证
  7. ]

(1)obtain_jwt_token:提交用户名和密码,后台判定是否合法,合法的话返回一个Token,否则认证失败

  1. - URL:/login/
  2. - Request:
  3. - 请求方法:POST(表单)
  4. - 请求参数:username:'',password:''
  5. Response:
  6. - 200,登录成功,返回{"token":"xxxxxx"}
  7. - 400,身份验证未通过

(2)verify_jwt_token:提交一个Token,后台判定Token是否过期

  1. - URL:/token-verify/
  2. - Request:
  3. - 请求方法:POST(application/json)
  4. - 请求参数:{"token":"xxxx"}
  5. Response:
  6. - 200,验证通过,Token合法
  7. - 400,Token不合法或已过期

4.携带token请求接口

  1. Token需要添加到HTTP请求头中,见下图
  2. Key:Authorization
  3. 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并添加字段即可。

  1. from django.contrib.auth.models import AbstractUser
  2. from django.db import models
  3. class UserInfo(AbstractUser):
  4. choice = (
  5. (1, 'admin'),
  6. (2, 'user')
  7. )
  8. user_type = models.IntegerField(choices=choice)

2.settings.py

修改Django Auth模块指向的model,改为我们重写后的model

AUTH_USER_MODEL = "user_auth.UserInfo"  # 重写user model后将用户认证指向重写后的model

3.保存更改

  1. python manage.py makemigrations
  2. python manage.py migrate

4.使用ModelViewSet和ModelSerializer

  1. class UserSerializer(serializers.ModelSerializer):
  2. def create(self, validated_data):
  3. instance = UserInfo.objects.create_user(**validated_data)
  4. return instance
  5. def update(self, instance, validated_data):
  6. """
  7. 只允许更新password字段
  8. :param instance:
  9. :param validated_data:
  10. :return:
  11. """
  12. instance.set_password(validated_data['password'])
  13. instance.save()
  14. return instance
  15. class Meta:
  16. model = UserInfo
  17. fields = ('id', 'username', 'user_type', 'date_joined', 'password')
  18. read_only_fields = ['date_joined']
  19. extra_kwargs = {'password': {'write_only': True}}
  20. class UserViewSet(ModelViewSet):
  21. permission_classes = (UserReadAdminWrite,)
  22. queryset = UserInfo.objects.all()
  23. serializer_class = UserSerializer
  24. def destroy(self, request, *args, **kwargs):
  25. instance = self.get_object()
  26. if request.user.id == instance.id:
  27. return Response({'ERROR': '不能删除正在使用的用户'} ,status=status.HTTP_403_FORBIDDEN)
  28. self.perform_destroy(instance)
  29. return Response(status=status.HTTP_204_NO_CONTENT)

JWT更多设置参数及用法见 http://getblimp.github.io/django-rest-framework-jwt/

0X02 

  1. REST_FRAMEWORK = {
  2. 'DEFAULT_PERMISSION_CLASSES': (
  3. # 设置访问权限为只读
  4. # 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
  5. # 设置访问权限为必须登录
  6. 'rest_framework.permissions.IsAuthenticated',
  7. ),
  8. 'DEFAULT_AUTHENTICATION_CLASSES': (
  9. 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
  10. # 使用rest_framework的界面需要下列认证模块
  11. #'rest_framework.authentication.SessionAuthentication',
  12. #'rest_framework.authentication.BasicAuthentication',
  13. ),
  14. }

如果加载了sessionAuthentication和BasicAuthentication模块,那么通过了JWT身份认证后,会设置一个cookide,session_id=xxx,使用该session_id,不使用Token也可以正常访问后台接口,如果只允许Token验证,需要注释掉session模块和basic模块。

源码:

Authentication类都必须继承于BaseAuthentication类:

  1. class BaseAuthentication(object):
  2. """
  3. All authentication classes should extend BaseAuthentication.
  4. """
  5. def authenticate(self, request):
  6. """
  7. Authenticate the request and return a two-tuple of (user, token).
  8. """
  9. raise NotImplementedError(".authenticate() must be overridden.")
  10. def authenticate_header(self, request):
  11. """
  12. Return a string to be used as the value of the `WWW-Authenticate`
  13. header in a `401 Unauthenticated` response, or `None` if the
  14. authentication scheme should return `403 Permission Denied` responses.
  15. """
  16. pass
'
运行

 

定义的Authentication类会在调用视图函数之前被调用:

  1. class APIView(View):
  2. # The following policies may be set at either globally, or per-view.
  3. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
  4. parser_classes = api_settings.DEFAULT_PARSER_CLASSES
  5. authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # !!!#
  6. throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
  7. permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
  8. content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
  9. metadata_class = api_settings.DEFAULT_METADATA_CLASS
  10. versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
  11. # Allow dependency injection of other settings to make testing easier.
  12. settings = api_settings
  13. schema = DefaultSchema()

 

dispatch方法应该是用于处理所有请求,dispatch函数:

  1. def dispatch(self, request, *args, **kwargs):
  2. """
  3. `.dispatch()` is pretty much the same as Django's regular dispatch,
  4. but with extra hooks for startup, finalize, and exception handling.
  5. """
  6. self.args = args
  7. self.kwargs = kwargs
  8. request = self.initialize_request(request, *args, **kwargs)
  9. self.request = request
  10. self.headers = self.default_response_headers # deprecate?
  11. try:
  12. self.initial(request, *args, **kwargs) # 该方法尤为重要,如下
  13. # Get the appropriate handler method
  14. if request.method.lower() in self.http_method_names:
  15. handler = getattr(self, request.method.lower(),
  16. self.http_method_not_allowed)
  17. else:
  18. handler = self.http_method_not_allowed
  19. response = handler(request, *args, **kwargs)
  20. except Exception as exc:
  21. response = self.handle_exception(exc)
  22. self.response = self.finalize_response(request, response, *args, **kwargs)
  23. return self.response
  1. def initial(self, request, *args, **kwargs):
  2. """
  3. 运行所有在视图函数前应该被调用的东西
  4. """
  5. self.format_kwarg = self.get_format_suffix(**kwargs)
  6. # Perform content negotiation and store the accepted info on the request
  7. neg = self.perform_content_negotiation(request)
  8. request.accepted_renderer, request.accepted_media_type = neg
  9. # Determine the API version, if versioning is in use.
  10. version, scheme = self.determine_version(request, *args, **kwargs)
  11. request.version, request.versioning_scheme = version, scheme
  12. # Ensure that the incoming request is permitted
  13. self.perform_authentication(request) # 用户认证
  14. self.check_permissions(request) # 权限认证
  15. self.check_throttles(request) # 访问频率限制

用户认证的具体方法:

perform_authentication:该函数只有一个request.user属性,利用了property的特性,通过属性转换为方法。

  1. def perform_authentication(self, request):
  2. """
  3. Perform authentication on the incoming request.
  4. Note that if you override this and simply 'pass', then authentication
  5. will instead be performed lazily, the first time either
  6. `request.user` or `request.auth` is accessed.
  7. """
  8. request.user
'
运行

调用的具体方法:

request.py的Request类的方法:

  1. @property
  2. def user(self):
  3. """
  4. Returns the user associated with the current request, as authenticated
  5. by the authentication classes provided to the request.
  6. """
  7. if not hasattr(self, '_user'):
  8. with wrap_attributeerrors():
  9. self._authenticate() # 调用所有认证方法
  10. return self._user

_authenticate方法:遍历加载的所有authentication方法并认证,如果认证通过就添加request.user属性

  1. def _authenticate(self):
  2. """
  3. Attempt to authenticate the request using each authentication instance
  4. in turn.
  5. """
  6. for authenticator in self.authenticators:
  7. try:
  8. user_auth_tuple = authenticator.authenticate(self)
  9. except exceptions.APIException:
  10. self._not_authenticated()
  11. raise
  12. if user_auth_tuple is not None:
  13. self._authenticator = authenticator
  14. self.user, self.auth = user_auth_tuple
  15. return
  16. self._not_authenticated()
'
运行

0X03 关于JWT的认证逻辑

1.登录,对应的是obtain_jwt_token这个视图,上面说过登录成功会返回一个Token

  1. from rest_framework_jwt.views import obtain_jwt_token
  2. urlpatterns = [
  3. path(r'login/', obtain_jwt_token),
  4. ]

在该视图使用的序列化器(JsonWebTokenSerializer)中,验证用户名密码的方法如下:

  1. def validate(self, attrs):
  2. credentials = {
  3. self.username_field: attrs.get(self.username_field),
  4. 'password': attrs.get('password')
  5. }
  6. if all(credentials.values()):
  7. user = authenticate(**credentials)
  8. if user:
  9. if not user.is_active:
  10. msg = _('User account is disabled.')
  11. raise serializers.ValidationError(msg)
  12. payload = jwt_payload_handler(user)
  13. return {
  14. 'token': jwt_encode_handler(payload),
  15. 'user': user
  16. }
  17. else:
  18. msg = _('Unable to log in with provided credentials.')
  19. raise serializers.ValidationError(msg)
  20. else:
  21. msg = _('Must include "{username_field}" and "password".')
  22. msg = msg.format(username_field=self.username_field)
  23. raise serializers.ValidationError(msg)

进一步查看autenticate(**credentials)方法:

  1. def authenticate(request=None, **credentials):
  2. """
  3. If the given credentials are valid, return a User object.
  4. """
  5. for backend, backend_path in _get_backends(return_tuples=True):
  6. try:
  7. inspect.getcallargs(backend.authenticate, request, **credentials)
  8. except TypeError:
  9. # This backend doesn't accept these credentials as arguments. Try the next one.
  10. continue
  11. try:
  12. user = backend.authenticate(request, **credentials)
  13. except PermissionDenied:
  14. # This backend says to stop in our tracks - this user should not be allowed in at all.
  15. break
  16. if user is None:
  17. continue
  18. # Annotate the user object with the path of the backend.
  19. user.backend = backend_path
  20. return user
'
运行

这个认证利用的是Django的AUTHENTICATION_BACKENDS,_get_backends方法会获取所有注册的认证后台:

  1. def _get_backends(return_tuples=False):
  2. backends = []
  3. for backend_path in settings.AUTHENTICATION_BACKENDS:
  4. backend = load_backend(backend_path)
  5. backends.append((backend, backend_path) if return_tuples else backend)
  6. if not backends:
  7. raise ImproperlyConfigured(
  8. 'No authentication backends have been defined. Does '
  9. 'AUTHENTICATION_BACKENDS contain anything?'
  10. )
  11. return backends

当调用 django.contrib.auth.authenticate() 方法时,Django将获取注册的所有认证后台,然后按顺序进行调用,直到某个认证后台认证成功,成功返回User对象,成功返回User对象后不会再进行后续的认证。认证后台的设置在settings.py中,默认没有该字段,但是具有默认值,默认值为(‘django.contrib.auth.backends.ModelBackend’,),使用Django默认的用户认证进行认证。

2.登录成功后将获取到的Token携带到HTTP请求头中,即可完成验证,下面说下后台对Token的验证机制。

验证机制其实非常简单,就是对Token进行反序列化,获取Token中存取的username和过期时间,如果无法解析或用户名不存在或已过期,都会造成认证失败。

  1. def authenticate(self, request):
  2. """
  3. Returns a two-tuple of `User` and token if a valid signature has been
  4. supplied using JWT-based authentication. Otherwise returns `None`.
  5. """
  6. jwt_value = self.get_jwt_value(request)
  7. if jwt_value is None:
  8. return None
  9. try:
  10. payload = jwt_decode_handler(jwt_value)
  11. except jwt.ExpiredSignature:
  12. msg = _('Signature has expired.')
  13. raise exceptions.AuthenticationFailed(msg)
  14. except jwt.DecodeError:
  15. msg = _('Error decoding signature.')
  16. raise exceptions.AuthenticationFailed(msg)
  17. except jwt.InvalidTokenError:
  18. raise exceptions.AuthenticationFailed()
  19. user = self.authenticate_credentials(payload)
  20. return (user, jwt_value)
'
运行

3.

用户可以自行编写认证后台。在settings.py中添加:

AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) # 此为默认配置

用户自己的认证后台直接继承rest_framework authentication.py中的BaseAuthentication,然后按照要求编写即可。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/893752
推荐阅读
相关标签
  

闽ICP备14008679号