Django测试开发学习笔记(二)

用户认证

1. cookie&session

  • cookie:因为http请求是无状态的,第一次和服务器连接后并且登录成功后,返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器。
    服务拿到cookie字符串就会进行解析为session={key:value}中的key,对应了找到这个用户是否登录is_login、用户名username等一些信息。
image
  • 设置cookie

    • 新建一个应用来演示cookie相关的操作

    • views.py代码

      class Login(View):
          def get(self,request):
              # 设置cookie,cookie是在响应中返回的,所以要对响应对象去设置cookie
              response=HttpResponse()
              # set_cookie参数
              # key:这个cookie的key,一般用JSessionID
              # value:这个cookie的value
              # max_age:最长的生命周期
              # expires:过期时间
              response.set_cookie("JSessionID","asdshgfkldsg999",max_age=120)
              response.content="请求成功"
              return response
      
    • 使用Jmeter调用该接口

      image
    • 新建一个HTTP Cookie管理器

  • 获取cookie

    • views.py

      def post(self,request):
          # 获取cookie
          print(request.COOKIES)
          return HttpResponse("获取cookie")
      
    • 使用Jmeter调用该接口

      image
  • 存储session

    • views.py

      def put(self,request):
          # 存储cookie
          cookie = request.COOKIES
          # session的key是用户名,value是cookie的值
          request.session["tim"] = cookie["JSessionID"]
          return HttpResponse("存储cookie成功")
      
    • 使用Jmeter调用该接口


      image
    • 查看数据库表Django_session,新增了session信息

      image

2. 修改session的存储机制

默认情况下,session数据是存储到数据库中的。当然也可以将session数据存储到其他地方。可以通过设置SESSION_ENGINE来更改session的存储位置,这个可以配置为以下几种方案:

  • django.contrib.sessions.backends.db:使用数据库。默认就是这种方案。
  • django.contrib.sessions.backends.file:使用文件来存储session。
  • django.contrib.sessions.backends.cache:使用缓存来存储session。想要将数据存储到缓存中,前提是你必须要在settings.py中配置好CACHES,并且是需要使用Memcached,而不能使用纯内存作为缓存。
  • django.contrib.sessions.backends.cached_db:在存储数据的时候,会将数据先存到缓存中,再存到数据库中。这样就可以保证万一缓存系统出现问题,session数据也不会丢失。在获取数据的时候,会先从缓存中获取,如果缓存中没有,那么就会从数据库中获取。
  • django.contrib.sessions.backends.signed_cookies:将session信息加密后存储到浏览器的cookie中。这种方式要注意安全,建议设置SESSION_COOKIE_HTTPONLY=True,那么在浏览器中不能通过js来操作session数据,并且还需要对settings.py中的SECRET_KEY进行保密,因为一旦别人知道这个SECRET_KEY,那么就可以进行解密。另外还有就是在cookie中,存储的数据不能超过4k。

3. 项目应用

使用django自带的认证、登录、退出账号的方法,记得自己配置下urls.py,不再展示

  • 先创建django自带鉴权相关表

    python manage.py makemigrations
    python manage.py migrate
    

    数据库会生成对应的用户权限相关表和session的表

    image
  • 禁掉CSRF中间件

    在django处理请求的过程中,需要经过中间件的过滤,涉及到跨站请求伪造时,django会把请求阻止过滤掉,所以我们要在setting.py中禁用跨站请求伪造的中间件,如果不禁用,好像会报一个403的错误。

    image
  • 配置settings.py

    SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 配置session引擎,表示你想把session存到什么地方(session可以存在数据库、缓存、文件里)
    SESSION_COOKIE_NAME = 'sessionid' # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
    SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
    SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False # 是否Https传输cookie
    SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输
    SESSION_COOKIE_AGE = 60*30 # Session的cookie失效日期(30分钟),不要写成1800,这样不易读,比如表示8小时,写为60*60*8
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期
    SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存
    
    image
  • 认证、登录、退出账号的代码

    from django.contrib.auth import authenticate, login, logout
    from django.contrib.auth.decorators import login_required
    from django.contrib.auth.models import User
    from django.http import HttpResponse, JsonResponse
    import json
    
    # 使用django自带的认证、登录、登出方法
    
    def signup(request):
        '''用户注册方法'''
        data = json.loads(request.body)
        # django的创建用户的方法,request传入username、password、email是必填的
        user = User.objects.create_user(username=data['username'], password=data['password'],
                                        email=data['email'])  # 使用User模块的create_user方法创建用户
        user.save()  # 保存
        # 返回一个响应信息
        info = {
            "code": '0000',
            'msg': "用户注册成功",
            'data': {
                "id": user.id,
                "username": user.username,
                "password": user.password
            }
        }
        return JsonResponse(info)  # 使用JsonResponse返回一个json字符串
    
    def user_login(request):
        '''
        用户登录方法
        :param request:
        :return:
        '''
        if request.method == 'GET':
            return HttpResponse("脑补一个登录页面")
        data = json.loads(request.body)
        user = authenticate(username=data["username"], password=data["password"])  # 返回一个user对象
        info = {
            "code": None,
            'msg': None
        }
        if user is not None:
            if user.is_active:  # is_active字段表示此条用户名和信息是否有效
                login(request, user)  # 调用django.contrib.auth模块的login
                info["code"] = "0000"
                info["msg"] = "登录成功"
            else:
                info["code"] = "9999"
                info["msg"] = "该账户不可用"
        else:
        info["code"] = "9999"
        info["msg"] = "用户名密码不正确"
    return JsonResponse(info)
    
    def user_logout(request):
        '''退出登录'''
        logout(request)  # 调用django.contrib.auth模块的logout方法
        info = {
            "code": "0000",
            'msg': "退出登录成功"
        }
        return JsonResponse(info)
    
    
    def test(request):
        return HttpResponse("脑补一个登录页面")
    
    
    @login_required(login_url='/login_demo/test')  # 此装饰器会校验会在访问该视图之前,校验session,若通过则继续,不通过则重定向到login_url指定的视图中去
    def test_user(request):
        '''
        测试登录
        :param request:
        :return:
        '''
        return HttpResponse('ok')
    
    • 使用Jmeter调用注册接口

      image

      image
    • 数据库auth_user表新增了一条用户信息

      image

      可见,用户密码是加密的。需要通过django的authenticate方法来验证用户名、密码是否正确。

    • 使用Jmeter调用登录接口

      image
      image

      接口响应头返回一个sessionid:

      image

      数据库表django_session中,新增一条记录,可以看出返回的sessionid就是这条记录的session_key

      image
    • 哪些视图需要用户认证呢?

      使用装饰器@login_required(login_url='/login_demo/test'),此装饰器会校验会在访问该视图之前,校验session,若通过则继续,不通过则重定向到login_url指定的视图中去

      session过期后,调用testuser,重定向到test


      image
      image

      登录之后,调用testuser


      image

      ps: 在每个视图上加一个装饰器其实很麻烦,此处只是了解原理,后面学习drf框架时,会使用新的方式进行用户认证。

  • 扩展内容
    • 用户组管理
    • 权限管理

缓存

(暂时不深入讲,会在drf部分讲我们开发所需要的一点知识)

中间件

1. 简介

在Django中,中间件(middleware)其实就是一个类,在请求到来和结束后,Django会根据自己的规则在合适的时机执行中间件中相应的方法。

  • 1.执行完所有的request方法到达执行流程;
  • 2.执行中间件的其他方法;
  • 3.经过所有response方法,返回客户端;

在一个项目中,如果想对全局所有视图函数或视图类起作用时,就可以在中间件中实现,比如想实现用户登录判断,基于用户的权限管理(RBAC)等都可以在Django中间件中来进行操作。

在settings.py文件中,注册该中间件(Django项目中的settings模块中,有一个MIDDLEWARE_CLASSES变量,其中每个元素都是一个中间件)

MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

  • 引子

    中间件是 Django用来处理请求和响应的钩子框架。它是一个轻量级的、底层级的“插件”系统,用于全局性地控制Django 的输入或输出,可以理解为内置的app或者小框架。

    在django.core.handlers.base模块中定义了如何接入中间件,这也是学习Django源码的入口之一。

    每个中间件组件负责实现一些特定的功能。例如,Django 包含一个中间件组件 AuthenticationMiddleware,它使用会话机制将用户与请求request关联起来。

    中间件可以放在你的工程的任何地方,并以Python路径的方式进行访问。

    Django 具有一些内置的中间件,并自动开启了其中的一部分,我们可以根据自己的需要进行调整。

  • 如何启用中间件

    若要启用中间件组件,请将其添加到 Django 配置文件settings.py的 MIDDLEWARE 配置项列表中。

    在 MIDDLEWARE 中,中间件由字符串表示。这个字符串以圆点分隔,指向中间件工厂的类或函数名的完整 Python 路径。下面是使用 django-admin startproject命令创建工程后,默认的中间件配置:

    MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    

    实际上在Django中可以不使用任何中间件,如果你愿意的话,MIDDLEWARE 配置项可以为空。但是强烈建议至少使用 CommonMiddleware。建议是保持默认的配置,这有助于你提高网站的安全性。

  • 中间件最关键的顺序问题

    MIDDLEWARE的顺序很重要,具有先后关系,因为有些中间件会依赖其他中间件。例如: AuthenticationMiddleware 需要在会话中间件中存储的经过身份验证的用户信息,因此它必须在 SessionMiddleware后面运行 。

    在请求阶段,调用视图之前,Django 按照定义的顺序执行中间件 MIDDLEWARE,自顶向下。

    如果某个层的执行过程认为当前的请求应该被拒绝,或者发生了某些错误,导致短路,直接返回了一个响应,那么剩下的中间件以及核心的视图函数都不会被执行。

  • Django内置的中间件

    Django内置了下面这些中间件,满足了我们一般的需求:

    • Cache

      缓存中间件

      如果启用了该中间件,Django会以CACHE_MIDDLEWARE_SECONDS 配置的参数进行全站级别的缓存。

    • Common

      通用中间件

2. 中间件方法

传统方式自定义中间件其实就是在编写五大钩子函数:

process_request(self,request)
process_response(self, request, response)
process_view(self, request, view_func, view_args, view_kwargs)
process_exception(self, request, exception)
process_template_response(self,request,response)

可以实现其中的任意一个或多个

image
image
image
image
image

3. 自定义中间件

有时候,为了实现一些特定的需求,我们可能需要编写自己的中间件。

在编写方式上,需要注意的是,当前Django版本2.2,存在两种编写的方式。一种是Django当前官网上提供的例子,一种是老版本的方式。本质上,两种方式其实是一样的。

我们先看一下传统的,也是技术文章最多,目前使用最多的方式。

Django 提供的 get_response 方法可能是一个实际视图(如果当前中间件是最后列出的中间件),或者是列表中的下一个中间件。我们不需要知道或关心它到底是什么,它只是代表了下一步要进行的操作。

两个注意事项:

  1. Django仅使用 get_response 参数初始化中间件,因此不能为 init() 添加其他参数。
  2. 与每次请求都会调用 call() 方法不同,当 Web 服务器启动后,init() 只被调用一次。
  • 新建一个应用来演示中间件

    python manage.py startapp middle_demo
    

    (自行配置urls.py)

  • 应用下新建一个middleware.py

    class SimpleMiddleware1: # 第一个中间件示例
        # 固定写法, 必须有
        def __init__(self, get_response):
            self.get_response = get_response
             # 配置和初始化
    
        # call方法中定义一些中间件的操作
        def __call__(self, request):
    
            # 在这里编写视图和后面的中间件被调用之前需要执行的代码
            # 这里其实就是旧的process_request()方法的代码
    
            print("SimpleMiddleware1的process_request()方法被调用")
            response = self.get_response(request)
    
            # 在这里编写视图调用后需要执行的代码
            # 这里其实就是旧的process_response()方法的代码
            print("SimpleMiddleware1的process_response()方法被调用")
    
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("SimpleMiddleware1的process_view()方法被调用")
    
        # 出现异常时,会执行这个代码
        def process_exception(self,request,exception):
            print("SimpleMiddleware1的process_exception()方法被调用")
    
        def process_template_response(self,request,response):
            # 默认不执行这个函数,除非views函数中返回的实例对象(注意这里这个词)中有render()方法
            print("SimpleMiddleware1的process_template_response()方法被调用")
            return response
    
    class SimpleMiddleware2: # 第二个中间件示例
        # 固定写法, 必须有
        def __init__(self, get_response):
            self.get_response = get_response
             # 配置和初始化
    
        # call方法中定义一些中间件的操作
        def __call__(self, request):
    
            # 在这里编写视图和后面的中间件被调用之前需要执行的代码
            # 这里其实就是旧的process_request()方法的代码
    
            print("SimpleMiddleware2的process_request()方法被调用")
            response = self.get_response(request)
    
            # 在这里编写视图调用后需要执行的代码
            # 这里其实就是旧的process_response()方法的代码
            print("SimpleMiddleware2的process_response()方法被调用")
    
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("SimpleMiddleware2的process_view()方法被调用")
    
        # 出现异常时,会执行这个代码
        def process_exception(self,request,exception):
            print("SimpleMiddleware2的process_exception()方法被调用")
    
        def process_template_response(self,request,response):
            # 默认不执行这个函数,除非views函数中返回的实例对象(注意这里这个词)中有render()方法
            print("SimpleMiddleware2的process_template_response()方法被调用")
            return response
    
    
  • 配置settings.py

    MIDDLEWARE = [
        'middle_demo.middleware.SimpleMiddleware1',
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        # 'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'middle_demo.middleware.SimpleMiddleware2',
    ]
    

    可以调用我们之前配置好的登录接口,在控制台上看到中间件的顺序:

    image

4. 中间件应用

  • 应用实例一:IP拦截
    如果我们想限制某些IP对服务器的访问,可以在settings.py中添加一个BLACKLIST(全大写)列表,将被限制的IP地址写入其中。

    然后,我们就可以编写下面的中间件了:

    from django.http import HttpResponseForbidden
    from django.conf import settings
    
    class BlackListMiddleware():
    
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
    
            if request.META['REMOTE_ADDR'] in getattr(settings, "BLACKLIST", []):
                return HttpResponseForbidden('<h1>该IP地址被限制访问!</h1>')
    
            response = self.get_response(request)
    
            return response
    
  • 应用实例二:DEBUG页面

    网站上线正式运行后,我们会将DEBUG改为 False,这样更安全。但是发生服务器5xx系列错误时,管理员却不能看到错误详情,调试很不方便。有没有办法比较方便地解决这个问题呢?

    • 普通访问者看到的是500错误页面
    • 管理员看到的是错误详情Debug页面

    利用中间件就可以做到!代码如下:

    import sys
    from django.views.debug import technical_500_response
    from django.conf import settings
    
    class DebugMiddleware():
    
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
    
            response = self.get_response(request)
    
            return response
    
        def process_exception(self, request, exception):
            # 如果是管理员,则返回一个特殊的响应对象,也就是Debug页面
            # 如果是普通用户,则返回None,交给默认的流程处理
            if request.user.is_superuser or request.META.get('REMOTE_ADDR') in settings.ADMIN_IP:
                return technical_500_response(request, *sys.exc_info())
    

    这里通过if判断,当前登录的用户是否超级管理员,或者当前用户的IP地址是否在管理员IP地址列表中。符合两者之一,即判断当前用户有权限查看Debug页面。

    接下来注册中间件,然后在测试视图中添加一行raise。再修改settings.py,将Debug设为False,提供ALLOWED_HOSTS = ["*"],设置比如ADMIN_IP = ['192.168.0.100'],然后启动服务器0.0.0.0:8000,从不同的局域网IP来测试这个中间件。

    正常情况下,管理员应该看到类似下面的Debug页面:

    RuntimeError at /midtest/
    No active exception to reraise
    Request Method: GET
    Request URL:    http://192.168.0.100:8000/midtest/
    Django Version: 2.0.7
    Exception Type: RuntimeError
    Exception Value:
    No active exception to reraise
    .....
    

    而普通用户只能看到:

    A server error occurred.  Please contact the administrator.
    
安装redis

安装参考:https://www.jianshu.com/p/bb7c19c5fc47

pycharm集成化插件Iedis

安装参考:https://blog.csdn.net/babados/article/details/78575145

因在mac上是付费的(不知道win要不要付费),故没有使用。

自己使用了叫做Redis桌面管理工具的软件:http://www.pc6.com/mac/486661.html

redis配置和测试
  • 安装django-redis

    pip install django-redis==4.11.0
    
  • 配置settings.py

    添加:

    CACHES = { # 配置缓存数据为redis
    "default": {
        "BACKEND": "django_redis.cache.RedisCache", 
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD":"12345" #redis密码,如果没设密码可以不用配置
            }
        }
    }
    
  • 测试redis安装是否成功

    # 终端输入
    
    # 启动redis 服务端
    redis-server
    
    # 启动redis客户端
    redis-cli
    
    from django_redis import get_redis_connection
    
    image
  • 获取缓存库链接

    cache = get_redis_connection("default") # default是settings.py中配置的项
    
    image
  • 添加缓存数据

    cache.set("abc","aaaa1234",30*60)
    
    image

    在redis客户端可以查看

    image
token
  • token简介

    在实现登录功能的时候,正常的B/S应用都会使用cookie+session的方式来做身份验证,后台直接向cookie中写数据,但是由于移动端的存在,移动端是没有cookie机制的,所以使用token可以实现移动端和客户端的token通信。

    • 验证流程

      整个基于Token的验证流程如下:

      • 客户端使用用户名跟密码请求登录
      • 服务器收到请求,去验证用户名和密码
      • 验证成功后,服务端会签发一个Token,再把这个Token发送到客户端
        客户端收到的Token以后可以把它存储起来,比如放在Cookie或LocalStorage里
      • 客户端每次向服务器发送其他请求的时候都要带着服务器签发的Token
      • 服务器收到请求,去验证客户端请求里面带着的Token,如果验证成功,就像客户端返回请求的数据
  • JWT标准

    构造Token的方法挺多的,可以说只要是客户端和服务器端约定好了格式,是想怎么写就怎么写的,然而还有一些标准写法,例如JWT读作/jot/,表示:JSON Web Tokens。

    JWT标准的Token有三个部分:

    • header
    • payload
    • signature

    三个部分会用点分割开,并且都会使用Base64编码,所以真正的Token看起来像这样:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
    
    • Header

      header部分主要是两部分内容,一个是Token的类型,另一个是使用的算法,比如下面的类型就是JWT,使用的算法是HS256:

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

      上面的内容要用 Base64 的形式编码一下,所以就变成这样:

      eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
      
    • Payload
      Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。下面是标准字段:

      • iss:Issuer,发行者
      • sub:Subject,主题
      • aud:Audience,观众
      • exp:Expiration time,过期时间
      • nbf:Not before
      • iat:Issued at,发行时间
      • jti:JWT ID
    • Signature

      JWT的最后一部分是Signature,这部分相当于前两段的摘要,用来防止其他人来篡改Token中的信息,在处理时可以首先将前两段生成的内容使用Base64生成一下再加盐然后利用MD5等摘要算法在生成一遍。

  • token生成

    • 服务端生成Token

      在服务端生成Token的时候,需要解决两个问题

      • 使用什么加密算法
      • Token如何存储
    • 加密算法

      这里的加密算法并不是MD5,SHA1这样的哈希算法,因为这种算法是无法解密的,只能用来生成摘要,在Django中内置了一个加密前面模块django.core.signing模块,可以用来加密和解密任何数据,使用签名模块的dumps和load函数来实现。

      from django.core import signing
      value = signing.dumps({"foo":"bar"})
      src = signing.loads(value)
      print(value)
      print(src)
      

      结果是

      eyJmb28iOiJiYXIifQ:1NMg1b:zGcDE4-TCkaeGzLeW9UQwZesciI 
      {‘foo’: ‘bar’}
      
  • token存储

    项目文件下新建一个utils目录,token.py

    import time
    from django.core import signing
    import hashlib
    from django_redis import get_redis_connection
    
    cache = get_redis_connection("default")
    HEADER = {'typ': 'JWT', 'alg': 'default'}
    KEY = 'DAYBREAK'
    SALT = 'www.daybreak.com'  # 生成摘要的盐
    TIME_OUT = 30 * 60  # 30min
    
    
    def encrypt(obj):
        """加密"""
        value = signing.dumps(obj, key=KEY, salt=SALT)
        value = signing.b64_encode(value.encode()).decode()
        return value
    
    
    def decrypt(src):
        """解密"""
        src = signing.b64_decode(src.encode()).decode()
        raw = signing.loads(src, key=KEY, salt=SALT)
        return raw
    
    
    # 重点掌握
    def create_token(username):
        """生成token信息"""
        # 1. 加密头信息
        header = encrypt(HEADER)
        # 2. 构造Payload
        payload = {"username": username, "iat": time.time()}
        payload = encrypt(payload)
        # 3. 生成签名
        md5 = hashlib.md5()
        md5.update(("%s.%s" % (header, payload)).encode())
        signature = md5.hexdigest()
        token = "%s.%s.%s" % (header, payload, signature)
        # 存储到缓存中
        cache.set(username, token, TIME_OUT)
        return token
    
    
    def get_payload(token):
        payload = str(token).split('.')[1]
        payload = decrypt(payload)
        return payload
    
    
    # 通过token获取用户名
    def get_username(token):
        payload = get_payload(token)
        return payload['username']
    
    
    # 重点掌握
    def check_token(token):
        if token is None:
            return False
        username = get_username(token)
        last_token = cache.get(username)
        if last_token:
            cache.expire(username, TIME_OUT)
            return True
        return False
    
    
    def delete_token(username):
        last_token = cache.get(username)
        if last_token:
            cache.delete(username)
            return True
        return False
    
    • 重点理解create_token()创建token、check_token()验证token

      from utils.token import *
      token = create_token("leitx") # 生成token
      check_token(token) # 验证token,认证成功返回True
      
      image
WSGIHTTP
基于中间件实现用户登录验证
  • utils目录下,新建login_middleware.py

    请求之前就要做处理,所以不需要实现后面这些方法process_viewprocess_exceptionprocess_template_response

    import json
    import re
    from utils.token import check_token
    
    # 设置白名单,这些请求不需要验证token,比如注册、登录接口
    from django.http import HttpResponse
    
    white_list = ['/middle_demo/login/', '/middle_demo/signup/']
    # 设置黑名单,作为示例
    black_list = ['/middle_demo/black/']
    
    
    class LoginMiddleware:
    
        # 固定写法, 必须有
        def __init__(self, get_response):
            self.get_response = get_response
            # 配置和初始化
    
        # call方法中定义一些中间件的操作
        def __call__(self, request):
    
            request_url = request.path_info  # 获取请求的url
    
            # 通过正则判断url
            # for p in white_list:
            #     r = re.compile(p)
            #     if r.match(request_url):
            #         response = self.get_response(request)
            #         return response
    
            # 不会正则的这样判断
            # 如果是白名单
            for p in white_list:
                if request_url in p:
                    response = self.get_response(request)
                    return response
    
            # 如果是黑名单
            for p in black_list:
                if request_url in p:
                    response = HttpResponse()
                    response.content = json.dumps({"code": "9999", "message": "非法请求", "data": None})
                    response["Content-Type"] = "application/json;charset=UTF-8"
                    return response
            # 获取请求头token的值
            token = request.META.get("HTTP_TOKEN")
            if check_token(token):
                response = self.get_response(request)
                return response
    
            response = HttpResponse()
            response.content = json.dumps({"code": "9999", "message": "用户未登录或token过期", "data": None})
            response["Content-Type"] = "application/json;charset=UTF-8"
            return response
    
    
    • request.META是一个Python字典包含了所有本次HTTP请求的Header信息,他把请求头的中的key都转为大写,并都加上HTTP_ 前缀,所以获取请求token值时,key使用"HTTP_TOKEN"
  • 配置settings.py中的中间件

    因为中间件代码中有return,触发return时,不会再执行后面的中间件request部分,所以一般自定义的中间件都放在最下面。

    MIDDLEWARE = [
    'middle_demo.middleware.SimpleMiddleware1',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middle_demo.middleware.SimpleMiddleware2',
    'utils.login_middleware.LoginMiddleware',
    ]
    
  • 在middle_demo的app中的view.py

    import json
    from django.contrib.auth import authenticate
    from django.contrib.auth.models import User
    from django.http import JsonResponse, HttpResponse
    from django.views import View
    from utils.token import create_token
    
    # Create your views here.
    def bye(request):
        print("想说拜拜")
        return HttpResponse("成功获取bye")
        
    class Signup(View):
        def post(self, request):
            data = json.loads(request.body)
            username = data.get("username",None)
            password = data.get("password",None)
            email = data.get("email",None)
            try:
                user = User.objects.create_user(username=username, password=password, email=email)
                token = create_token(user.username)
                return JsonResponse({"code": "0000", "message": "注册成功", "data": token})
            except:
                return JsonResponse({"code": "9999", "message": "用户已注册或信息缺失", "data": None})
                
    class Login(View):
        def post(self, request):
            data = json.loads(request.body)
            username = data['username']
            password = data['password']
            # 校验用户名和密码,成功返回user对象,失败返回None
            user = authenticate(username=username, password=password)
            if user:
                token = create_token(user.username)
                return JsonResponse({"code": "0000", "message": "登录成功", "data": token})
            else:
                return JsonResponse({"code": "9999", "message": "用户名或密码不正确", "data": None})
    
    
  • Jmeter调用白名单、其他接口对比

    • 踩坑:代码中写token是存在redis中的,所以一定要开启redis服务。

    白名单: '/middle_demo/signup/'


    image

    白名单:'/middle_demo/login/'


    image

    非白名单,没有加token


    image

    非白名单,加token


    image
格式化响应输出

Django中请求的生命周期

image

步骤

第一步:浏览器发起请求

第二步:WSGI创建socket服务端,接收请求(Httprequest)

第三步:中间件处理请求

第四步:url路由,根据当前请求的URL找到视图函数

第五步:view视图,进行业务处理(ORM处理数据,从数据库取到数据返回给view视图;view视图将数据渲染到template模板;将数据返回)

第六步:中间件处理响应

第七步:WSGI返回响应(HttpResponse)

第八步:浏览器渲染

FBV模式和CBV模式(了解)

一个url对应一个视图函数,这个模式叫做FBV(Function Base Views),即函数视图

一个url对应一个类,这个模式叫做CBV(Class Base views),即类视图

restframework框架

restful 规范

Restful API是目前比较成熟的一套互联网应用程序的API设计理念,Rest是一组架构约束条件和原则,如何Rest约束条件和原则的架构,我们就称为Restful架构,Restful架构具有结构清晰、符合标准、易于理解以及扩展方便等特点,受到越来越多网站的采用!

Restful API接口规范包括以下部分:

  1. 协议

API与用户的通信协议,总是使用HTTPs协议。

  1. 域名

应该尽量将API部署在专用域名之下,如https://api.专属域名.com;如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下,如https://专属域名.com/api/

  1. 版本

可以将版本号放在HTTP头信息中,也可以放入URL中,如https://api.专属域名.com/v1/

  1. 路径

路径是一种地址,在互联网上表现为网址,在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数,如https://api.专属域名.com/v1/students

  1. HTTP****动词

对于资源的具体操作类型,由HTTP动词表示,HTTP动词主要有以下几种,括号中对应的是SQL命令。

    1. GET(SELECT):从服务器取出资源(一项或多项);
    1. POST(CREATE):在服务器新建一个资源;
    1. PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源);
    1. PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性);
    1. DELETE(DELETE):从服务器删除资源;
    1. HEAD:获取资源的元数据;
    1. OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
  1. 过滤信息

如果记录数量很多,服务器不可能都将它们返回给用户,API会提供参数,过滤返回结果,常见的参数有:

    1. ?limit=20:指定返回记录的数量为20;
    1. ?offset=8:指定返回记录的开始位置为8;
    1. ?page=1&per_page=50:指定第1页,以及每页的记录数为50;
    1. ?sortby=name&order=asc:指定返回结果按照name属性进行升序排序;
    1. ?animal_type_id=2:指定筛选条件。
  1. 状态码

服务器会向用户返回状态码和提示信息,以下是常用的一些状态码:

    1. 200 OK - [GET]:服务器成功返回用户请求的数据;
    1. 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功;
    1. 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务);
    1. 204 NO CONTENT - [DELETE]:用户删除数据成功;
    1. 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作;
    1. 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误);
    1. 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的;
    1. 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作;
    1. 406 Not Acceptable - [GET]:用户请求的格式不可得;
    1. 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的;
    1. 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误;
    1. 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
  1. 错误处理

如果状态码是4xx,就会向用户返回出错信息,一般来说,返回的信息中将error作为键名,出错信息作为键值。

  1. 返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范:

    1. GET /collection:返回资源对象的列表(数组);
    1. GET /collection/resource:返回单个资源对象;
    1. POST /collection:返回新生成的资源对象;
    1. PUT /collection/resource:返回完整的资源对象;
    1. PATCH /collection/resource:返回完整的资源对象;
    1. DELETE /collection/resource:返回一个空文档。
  1. Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

以上是Restful API设计应遵循的十大规范,除此之外,Restful API还需注意身份认证应该使用OAuth 2.0框架,服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349