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通过装饰器检查函数参数数据类型的方法
Mar 13 Python
python实现雨滴下落到地面效果
Jun 21 Python
纯用NumPy实现神经网络的示例代码
Oct 24 Python
python 获取页面表格数据存放到csv中的方法
Dec 26 Python
实时获取Python的print输出流方法
Jan 07 Python
python 并发编程 阻塞IO模型原理解析
Aug 20 Python
执行Django数据迁移时报 1091错误及解决方法
Oct 14 Python
keras 使用Lambda 快速新建层 添加多个参数操作
Jun 10 Python
Visual Studio Code搭建django项目的方法步骤
Sep 17 Python
python爬虫scrapy图书分类实例讲解
Nov 23 Python
python 模拟登录B站的示例代码
Dec 15 Python
Python 可视化神器Plotly详解
Dec 26 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下防止单引号,双引号在接受页面转义的设置方法
2008/09/25 PHP
PHP面向对象分析设计的61条军规小结
2010/07/17 PHP
php+mysql实现无限分类实例详解
2015/01/15 PHP
基于JQuery 选择器使用说明介绍
2013/04/18 Javascript
JS中attr和prop属性的区别以及优先选择示例介绍
2014/06/30 Javascript
jquery判断浏览器后退时候弹出消息的方法
2014/08/11 Javascript
Javascript中的数据类型之旅
2015/10/18 Javascript
JS与jQuery遍历Table所有单元格内容的方法
2015/12/07 Javascript
给easyui datebox扩展一个清空的实例
2016/11/09 Javascript
Bootstrap组件之下拉菜单,多级菜单及按钮布局方法实例
2017/05/25 Javascript
基于input动态模糊查询的实现方法
2017/12/12 Javascript
浅谈微信JS-SDK 微信分享接口开发(介绍版)
2018/08/15 Javascript
JavaScript中的类型检查
2020/02/03 Javascript
微信小程序连接服务器展示MQTT数据信息的实现
2020/07/14 Javascript
原生JavaScript实现换肤
2021/02/19 Javascript
Python实现去除代码前行号的方法
2015/03/10 Python
Python处理Excel文件实例代码
2017/06/20 Python
Python3.6简单的操作Mysql数据库的三个实例
2018/10/17 Python
python使用循环打印所有三位数水仙花数的实例
2018/11/13 Python
python判断单向链表是否包括环,若包含则计算环入口的节点实例分析
2019/10/23 Python
Python关于__name__属性的含义和作用详解
2020/02/19 Python
python在linux环境下安装skimage的示例代码
2020/10/14 Python
纯CSS3实现3D旋转书本效果
2016/03/21 HTML / CSS
css3发光搜索表单分享
2014/04/11 HTML / CSS
html5的canvas实现3d雪花飘舞效果
2013/12/27 HTML / CSS
金智子午JAVA面试题
2015/09/04 面试题
新闻编辑专业自荐信
2014/07/02 职场文书
三峡导游词
2015/01/31 职场文书
2015年乡镇环保工作总结
2015/04/22 职场文书
2015年消防工作总结
2015/04/24 职场文书
基层组织建设年活动总结
2015/05/09 职场文书
2016年公司新年寄语
2015/08/17 职场文书
大学生如何逃脱“毕业季创业队即散伙”魔咒?
2019/08/19 职场文书
JavaWeb 入门篇:创建Web项目,Idea配置tomcat
2021/07/16 Java/Android
instantclient客户端 连接oracle数据库
2022/04/26 Oracle
使用HBuilder制作一个简单的HTML5网页
2022/07/07 HTML / CSS