django drf框架中的user验证以及JWT拓展的介绍


Posted in Python onAugust 12, 2019

登录注册是几乎所有网站都需要去做的接口,而说到登录,自然也就涉及到验证以及用户登录状态保存,最近用DRF在做的一个关于网上商城的项目中,引入了一个拓展DRF JWT,专门用于做验证和用户状态保存。这个拓展比传统的CSRF更加安全。先来介绍一下JWT认证机制吧!

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准( (RFC 7519 ).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

基于token的鉴权机制

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程上是这样的:

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持 CORS(跨来源资源共享) 策略,一般我们在服务端这么做就可以了 Access-Control-Allow-Origin: * 。

那么我们现在回到JWT的主题上。

JWT的构成

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

jwt的头部承载两部分信息:1,声明类型,这里是jwt,2声明加密的算法 通常直接使用 HMAC SHA256。完整的头部就像下面这样的JSON:

{
 'typ': 'JWT',
 'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。

如 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9。

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分1标准中注册的声明,2公共的声明,3私有的声明。标准中注册的声明 (建议但不强制使用) :1 iss: jwt签发者,2 sub: jwt所面向的用户,3 aud: 接收jwt的一方,4 exp: jwt的过期时间,这个过期时间必须要大于签发时间,5 nbf: 定义在什么时间之前,该jwt都是不可用的,6 iat: jwt的签发时间,7 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。定义一个payload:

{
 "sub": "1234567890",
 "name": "John Doe",
 "admin": true
}

然后将其进行base64加密,得到JWT的第二部分。

如 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9。

JWT的第三部分是一个签证信息,这个签证信息由三部分组成:1 header (base64后的),2 payload (base64后的),3 secret。这个部分需要base64加密后的header和base64加密后的payload使用 . 连接组成的字符串,然后通过header中声明的加密方式进行加盐 secret 组合加密,然后就构成了jwt的第三部分。

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

首先需要安装拓展  pip install djangorestframework-jwt,然后在django进行配置, JWT_EXPIRATION_DELTA 指明token的有效期。

REST_FRAMEWORK = {
 'DEFAULT_AUTHENTICATION_CLASSES': (
  'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
  'rest_framework.authentication.SessionAuthentication',
  'rest_framework.authentication.BasicAuthentication',
 ),
}

JWT_AUTH = {
 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}

Django REST framework JWT 扩展的说明文档中提供了手动签发JWT的方法

from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)

在注册时,引入上述代码,签发JWT即可。而对于登录,JWT拓展提供了内置的视图,在urls中添加对于路由即可。

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
 url(r'^authorizations/$', obtain_jwt_token),
]

虽然写起来很简单,但是内部其实做了很多的事,今天就来详细研究一下,源代码内部做了哪些事情。

当用户登录,会以post形式发请求到后端,会访问 obtain_jwt_token中的post方法,在源代码中可以看到 obtain_jwt_token = ObtainJSONWebToken.as_view(),跟我们写的类视图十分类似,这是一个内部已经写好的类视图。

class ObtainJSONWebToken(JSONWebTokenAPIView):
 """
 API View that receives a POST with a user's username and password.

 Returns a JSON Web Token that can be used for authenticated requests.
 """
 serializer_class = JSONWebTokenSerializer

该类并未定义任何方法,所以对应的post方法应该写在父类,下面是父类中的post方法

def post(self, request, *args, **kwargs):
  serializer = self.get_serializer(data=request.data)

  if serializer.is_valid():
   user = serializer.object.get('user') or request.user
   token = serializer.object.get('token')
   response_data = jwt_response_payload_handler(token, user, request)
   response = Response(response_data)
   if api_settings.JWT_AUTH_COOKIE:
    expiration = (datetime.utcnow() +
        api_settings.JWT_EXPIRATION_DELTA)
    response.set_cookie(api_settings.JWT_AUTH_COOKIE,
         token,
         expires=expiration,
         httponly=True)
   return response

  return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

上述方法返回一个Response对象,经过一系列操作返回到前端,访问结束。

而在DRF框架中,在调用视图之前,就会进行相应的验证操作。想要了解整个过程,需要我们从源代码中一步步去探索。当前端发起一个请求到后端,会根据路由访问对象的视图类的as_view()方法,该方法会接着调用dispatch()方法,APIView是DRF中所有视图类的父类,可以看一下他的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

可以看到,到请求进来,会调用self.initalize_request()方法对请求进行处理。

def initialize_request(self, request, *args, **kwargs):
  """
  Returns the initial request object.
  """
  parser_context = self.get_parser_context(request)

  return Request(
   request,
   parsers=self.get_parsers(),
   authenticators=self.get_authenticators(),
   negotiator=self.get_content_negotiator(),
   parser_context=parser_context
  )

我们需要关注的只有 authenticators=self.get_authenticators()这一句,

def get_authenticators(self):
  """
  Instantiates and returns the list of authenticators that this view can use.
  """
  return [auth() for auth in self.authentication_classes]

接着往上照,可以看到类属性 permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES,这就是要什么我们要在django配置中加入DEFAULT_PERMISSION_CLASSES配置,上述方法会遍历我们在配置中写到的列表,拿到里面的验证类,并进行实例化,并将生成的对象装在一个新的列表中,保存在新生成的Request对象中。然后我们接着看dispatch方法,在实际调用视图类中的对应方法前,还调用了self.initial()方法进行一些初始化操作。

def initial(self, request, *args, **kwargs):
  """
  Runs anything that needs to occur prior to calling the method handler.
  """
  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)

我们需要关注的是最后三排,分别调用了三个方法,进行身份验证,权限验证和分流操作,这里我们只关注 身份验证方法,self.perform_authentication(),该方法内只有一句代码request.user,看起来像是在调用request对象的user属性,其实不然,我们可以到DRF框架的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

user方法经过property装饰器装饰后,就可以像一个属性一样调用该方法,该方法在Request对象中存在对应的user时会直接返回,若用户登陆时,Request对象中没有对应的user,所以代码会走if判断里面,我们只需要关注方法self._authenticate()的调用即可。

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()

可以看到,该方法会遍历我们在之前处理Request对象时传入的装着验证类对象的列表,并调用验证类的authenticate()方法,若验证成功生成对应的self.user和self.auth并直接return,往上则直接将生成的self.user进行返回,若验证内部出错,会调用 self._not_ authenticated(), 并抛出错误,往上看,在dispatch方法中,若初始化方法出错,则进行捕获,并调用self.handle_exception()方法生成一个Response对象进行返回,不会执行视图类中对应的方法,则调用对于的是self._not_authenticated()。

def _not_authenticated(self):
  """
  Set authenticator, user & authtoken representing an unauthenticated request.

  Defaults are None, AnonymousUser & None.
  """
  self._authenticator = None

  if api_settings.UNAUTHENTICATED_USER:
   self.user = api_settings.UNAUTHENTICATED_USER()
  else:
   self.user = None

  if api_settings.UNAUTHENTICATED_TOKEN:
   self.auth = api_settings.UNAUTHENTICATED_TOKEN()
  else:
   self.auth = None

UNAUTHENTICATED_USER在django默认配置中为一个匿名用户的类,UNAUTHENTICATED_TOKEN默认为None,若所有验证都为通过或者某一验证过程中出错,则生成一个匿名用户,并将self.auth设置为None。

综上所述,在请求执行之前,DRF框架会根据我们在配置文件中配置的验证类对用户进行身份验证,若未通过验证,则会生成一个匿名用户,验证通过,则生成对应的用户。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python中精确输出JSON浮点数的方法
Apr 18 Python
解决Python plt.savefig 保存图片时一片空白的问题
Jan 10 Python
django解决跨域请求的问题详解
Jan 20 Python
Python+PyQt5实现美剧爬虫可视工具的方法
Apr 25 Python
python图像处理模块Pillow的学习详解
Oct 09 Python
Python列表list常用内建函数实例小结
Oct 22 Python
python常见字符串处理函数与用法汇总
Oct 30 Python
利用PyQt中的QThread类实现多线程
Feb 18 Python
tensorflow中tf.reduce_mean函数的使用
Apr 19 Python
Python操作PostgreSql数据库的方法(基本的增删改查)
Dec 29 Python
利用python查看数组中的所有元素是否相同
Jan 08 Python
解决import tensorflow导致jupyter内核死亡的问题
Feb 06 Python
python中eval与int的区别浅析
Aug 11 #Python
将Python文件打包成.EXE可执行文件的方法
Aug 11 #Python
python多线程同步实例教程
Aug 11 #Python
pandas的to_datetime时间转换使用及学习心得
Aug 11 #Python
python中时间转换datetime和pd.to_datetime详析
Aug 11 #Python
Python时间序列缺失值的处理方法(日期缺失填充)
Aug 11 #Python
python3实现带多张图片、附件的邮件发送
Aug 10 #Python
You might like
PHP学习散记_编码(json_encode 中文不显示)
2011/11/10 PHP
php和editplus正则表达式去除空白行
2015/04/17 PHP
PHP Reflection API详解
2015/05/12 PHP
一个完整的PHP类包含的七种语法说明
2015/06/04 PHP
用javascript实现给出的盒子的序列是否可连为一矩型
2007/08/30 Javascript
传智播客学习之java 反射
2009/11/22 Javascript
ImageZoom 图片放大镜效果(多功能扩展篇)
2010/04/14 Javascript
js控制frameSet示例
2013/09/10 Javascript
深入理解JavaScript内置函数
2016/06/03 Javascript
AngularJS 过滤器的简单实例
2016/07/27 Javascript
jQuery插件easyUI实现通过JS显示Dialog的方法
2016/09/16 Javascript
jQuery Ztree行政地区树状展示(点击加载)
2016/11/09 Javascript
bootstrap组件之导航组件使用方法
2017/01/19 Javascript
JavaScript实现的超简单计算器功能示例
2017/12/23 Javascript
微信小程序实现MUI数字输入框效果
2018/01/31 Javascript
Vue render函数实战之实现tabs选项卡组件
2019/04/22 Javascript
js中的面向对象之对象常见创建方法详解
2019/12/16 Javascript
JQuery常用选择器功能与用法实例分析
2019/12/23 jQuery
vue移动端下拉刷新和上滑加载
2020/10/27 Javascript
Python基础练习之用户登录实现代码分享
2017/11/08 Python
python 实现登录网页的操作方法
2018/05/11 Python
python实现年会抽奖程序
2019/01/22 Python
PyQt5实现QLineEdit添加clicked信号的方法
2019/06/25 Python
浅析Python3 pip换源问题
2020/01/06 Python
Python 实现判断图片格式并转换,将转换的图像存到生成的文件夹中
2020/01/13 Python
Python 微信公众号文章爬取的示例代码
2020/11/30 Python
美国马匹用品和骑马配件购物网站:Horse.com
2018/01/08 全球购物
卡骆驰英国官网:Crocs英国
2019/08/22 全球购物
测试时代收集的软件测试面试题
2013/09/25 面试题
网络信息管理员岗位职责
2014/01/05 职场文书
同学聚会策划方案
2014/06/06 职场文书
小学美术兴趣小组活动总结
2014/07/07 职场文书
党员群众路线对照检查材料
2014/08/31 职场文书
主婚人致辞精选
2015/07/28 职场文书
利用前端HTML+CSS+JS开发简单的TODOLIST功能(记事本)
2021/04/13 Javascript
Windows和Linux上部署Golang并运行程序
2022/04/22 Servers