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

登录注册是几乎所有网站都需要去做的接口,而说到登录,自然也就涉及到验证以及用户登录状态保存,最近用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框架会根据我们在配置文件中配置的验证类对用户进行身份验证,若未通过验证,则会生成一个匿名用户,验证通过,则生成对应的用户。

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

(0)

相关推荐

  • Django框架之DRF 基于mixins来封装的视图详解

    基础视图 示例环境搭建:新建一个Django项目,连接Mysql数据库,配置路由.视图函数.序列化单独创建py文件 # 配置路由 from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^PublishView/', views.PublishView.as_vi

  • django DRF图片路径问题的解决方法

    前言 其实就是Django RESTful Framework,RESTful一种API的命名风格,主要因为前后端分离开发出现,前后端分离: 用户访问静态文件的服务器,数据全部由ajax请求给到,RESTful风格:数据应该是名词,而动词由HTTP的请求方式来体现,RESTful风格的API给前端返回 结果对象,无论什么请求方式 本文主要介绍了关于django DRF图片路径问题,下面话不多说了,来一起看看详细的介绍吧 问题描述: 为什么DRF中有时候返回的json中图片是带域名的,有时候是不带

  • DRF跨域后端解决之django-cors-headers的使用

    在使用django-rest-framework开发项目的时候我们总是避免不了跨域的问题,因为现在大多数的项目都是前后端分离,前后端项目部署在不同的web服务器上,因为我们是后端程序员,因此我要通过后端的程序实现跨域.当然如果前端框架是Vue的话,则可以代理服务实现跨域,我也就知道一点点,如果有兴趣,大家可以自行搜索哦. DRF后端实现跨域我们使用一个第三方扩展--- djangocorsheaders 安装 pip install django-cors-headers 注册 INSTALLE

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

    登录注册是几乎所有网站都需要去做的接口,而说到登录,自然也就涉及到验证以及用户登录状态保存,最近用DRF在做的一个关于网上商城的项目中,引入了一个拓展DRF JWT,专门用于做验证和用户状态保存.这个拓展比传统的CSRF更加安全.先来介绍一下JWT认证机制吧! Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准( (RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景.JWT的声明一般被用来在

  • django drf框架自带的路由及最简化的视图

    django-drf框架自带的路由以及最简化的视图,具体内容如下所示: 路由 一.导入模块 from rest_framework.routers import SimpleRouter 二.初始化路由对象 router = SimpleRouter() 三.创建路由 router = SimpleRouter() # 注册各种接口路由 router.register('cars', views.CarModelViewSet, base_name='car') #car为链接的开头,views

  • Django的session中对于用户验证的支持

    用户与Authentication 通过session,我们可以在多次浏览器请求中保持数据, 接下来的部分就是用session来处理用户登录了. 当然,不能仅凭用户的一面之词,我们就相信,所以我们需要认证. 当然了,Django 也提供了工具来处理这样的常见任务(就像其他常见任务一样). Django 用户认证系统处理用户帐号,组,权限以及基于cookie的用户会话. 这个系统一般被称为 auth/auth (认证与授权)系统. 这个系统的名称同时也表明了用户常见的两步处理. 我们需要 验证 (

  • Python的Django REST框架中的序列化及请求和返回

    序列化Serialization 1. 设置一个新的环境 在我们开始之前, 我们首先使用virtualenv要创建一个新的虚拟环境,以使我们的配置和我们的其他项目配置彻底分开. $mkdir ~/env $virtualenv ~/env/tutorial $source ~/env/tutorial/bin/avtivate 现在我们处在一个虚拟的环境中,开始安装我们的依赖包 $pip install django $pip install djangorestframework $pip i

  • 设计模式在Spring框架中的应用汇总

    这篇文章主要介绍了设计模式在Spring框架中的应用汇总,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在开始正文之前,请你先思考几个问题: 你项目中有使用哪些 GOF 设计模式 说一说 GOF 23 种设计模式的设计理念 说说 Spring 框架中如何实现设计模式 假设我是面试官问起了你这些面试题,你该如何回答呢,请先思考一分钟. 好的,我们开始进入正题.设计模式实践里面提供了许多经久不衰的解决方案和最佳方案.这里,GOF 设计模式主要分为三

  • vue与django(drf)实现文件上传下载功能全过程

    目录 文件上传功能 上传后端部分 上传前端部分(vue添加vue.js和node.js,设置eslint) 文件下载功能 下载后端部分 下载前端部分 总结 文件上传功能 上传后端部分 (一)Models.py from django.db import models class FilesModel(models.Model): //模型名(默认表名) name = models.CharField(max_length=20, default='') //name,fle是字段名(file为上

  • Django ORM 查询表中某列字段值的方法

    1.什么是ORM ORM 全拼Object-Relation Mapping. 中文意为 对象-关系映射. 在MVC/MVT设计模式中的Model模块中都包括ORM 2.ORM优势 (1)只需要面向对象编程, 不需要面向数据库编写代码. 对数据库的操作都转化成对类属性和方法的操作. 不用编写各种数据库的sql语句. (2)实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异. 不在关注用的是mysql.oracle...等. 通过简单的配置就可以轻松更换数据库, 而不需要修改代码. 3.

  • ABP框架中的事件总线功能介绍

    目录 事件总线 关于事件总线 为什么需要这个东西 事件总线创建过程 订阅事件 事件 发布事件 全局异常加入事件总线功能 创建事件 订阅事件 发布事件 测试 记录事件 事件总线 关于事件总线 ABP 中,为了方便进程间通讯,给开发者提供了一个叫 事件总线 的功能,事件总线分为 本地事件总线.分布式事件总线,本篇文章讲的是 本地事件总线,系列教程中暂时不考虑讲解 分布式事件总线. 事件总线 需要使用 Volo.Abp.EventBus 库,ABP 包中自带,不需要额外引入. 事件总线是通过 订阅-发

  • SpringBoot框架中Mybatis-plus的简单使用操作汇总

    Mybatis-plus 官网地址:https://baomidou.com/ 配置mysql 在配置文件连接mysql spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/cat_house?serverTimezone=GMT%2B8 spring.datasource.username=username spring.da

  • C#在MEF框架中手动导入依赖模块

    对于简单的场景来讲,在MEF中导入依赖模块非常简单,只要用ImportAttribute标记依赖的成员,MEF模块会自动找到并创建该模块.但有的时候我们依赖的模块是上下文相关的,此时MEF框架的自动组装满足不了我们的需求了,这里以我之前的文章的一个Log插件为例: class HostModule { [Import] ILogger logger = null; public string Name { get; private set; } public HostModule(string

随机推荐