zoukankan      html  css  js  c++  java
  • 多人博客项目(Django模板+DTL语法+注册接口设计和实现)

    1、模板

      如果使用react实现前端页面,其实Django 就没有必须使用模板(前后端分离),它其实就是一个后台服务程序,接受请求,响应数据,接口设计就可以是纯粹的Restful 风格。

      模板的目的就是为了可视化,将数据按照一定布局格式输出,而不是为了数据处理,多以一般不会有复杂的处理逻辑,模板的引入实现了业务逻辑和显示格式的分离,这样,在开发中,就可以分工协作,页面开发完成页面布局设计,后台开发王城数据处理逻辑的实现。

      Python的模板引擎默认使用Django template Language(DTL)构建。

    2、模板配置

      在settings.py中 , 设置模板项目的路径

      

       DIRS 列表,定义模板文件的搜索路径顺序。

      APP_DIRS 是否运行在每个已经安装的应用中查找模板,应用自己目录下有temolates目录,例如:

      django/contrib/admin/temolates. 如果应用需要可分离,可重用,建议吧模板放到应用目录下BASE_DIR

      是项目根目录,os.path.join(BASE_DIR, 'templates')就是在manage.py 这一层建立一个目录templates,这个路径就是以后默认找模板的地方。

    3、模板渲染

      模板页:测试,所以自己写一个,并放到根目录下的 templates

        

        但是这样,前后端在一起 开发

      模板处理:

        2个步骤:

          1、加载模板

            模板是一个文件,需要从磁盘读取并加载,需要将模板放置templeates

          2、渲染

            模板需要使用内容数据来渲染,生成HTML 文件内容

        测试:模板不需要做任何插入数据,只是显示

        

        测试:对模板进行挖空,后台执行时,插入数据

        

        测试:使用短格式,即简单方式;

        

        render的源代码:

          

        render_to_string()是核心方法,其实就是拿数据替换HTML中的指定位置后返回一个字符串

     4、DTL 语法  

    • 变量
    • 标签
    • 注释
    • 过滤器

      1、变量

        语法:{{  variable }}

        变量名由字母、数字、下划线、点号组成

        点号使用的时候,例如foo.bar,遵循以下顺序:

      1. 字典查找,例如foo[" bar" ],把foo当作字典,bar 当作key
      2. 属性或方法的查找,例如 foo.bar ,把foo当作对象,bar 当作属性或方法
      3. 数字索引查找,例如 foo[bar] 把foo当作列表一样,使用索引访问  

          测试:

          

          测试:2

          

          如果变量未能找到,则缺省插入空字符串

          在模板中调用方法,不能加小括号,自然也就不能传递参数。     

          {{dict.keys}}<br />

      2、标签

        if/ else 标签

        基本语法格式如下:有开始,就有关闭 endif

               或者       

        条件也支持 and, or, not

        注意:因为这些标签是断开的,所以不能像python那样使用缩进就可以表示出来,必须有结束标签,例如:endif, endfor

        for 标签:https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#for

          

          

          

          给标签增加一个reversed 使得该列表别反向迭代。

            

          可以嵌套使用{% for %}标签

            

       ifequal , ifnotequal 标签     

         {% ifequal %} 标签比较两个值,当他们相等时,显示在{% ifequal%} 和 {% endifequal %} 之中所有的值           

          

        和 if 类似, 也支持可选的 else 标签

          

       其他标签

         csrf_token 用于跨站请求伪造保护,防止跨站攻击

         {% csrf_token %}

      3、注释标签  

        单行注释 {# #}

        多行注释 {% comment %}。。。。。{% endcomment %}

      4、过滤器

        模板过滤器可以在变量被显示前修改它。

        语法: {{ 变量 | 过滤器}}

        过滤器使用管道字符 |

        例如: {{name | lower}}  , {{name]} 变量被过滤器 lower处理后文档大写转换为小写

        过滤器管道可以被套接 , 一个过滤器管道的输出又可以作为下一个管道的输入:

            {{name|first|upper}} 将变量的第一个元素转换为大写

        过滤器传参:

         有些过滤器可以传递参数,过滤器的参数跟随冒号之后,并总是以双引号包含

          例如:{{bio | truncacteword: "30"}} 截取显示变量bio的前30个单词

              {{name | join:","}} j将name的所有元素使用 逗号 连接起来。

        其他过滤器:

          

          date:按照指定的格式字符串参数格式化date,或者datetime对象,实例:

            {{ name.date | date: "Y n j"}}

            Y: 2018年

            n:1-12月

            j:1-31日

            时间的格式字符串查看:https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#date

    测试:使用下面的额字典my_dict 的c的列表,在模板网页中列表ul 输出多行数据:

      1、奇偶行颜色不同

      2、每行有行号

      3、列表中所有的数据都增大100

       方式1:

         

      方式2:将上面的组合一下

        

          

     测试:打印九成九的乘法表

        方式1: 视图提供函数:将 i * j = x 作为字符串,views.py提供数据,index.html显示

        

        

       方式2 :内建标签:widthratio:直接通过index.html 计算得出结果,并显示:

        widthratio本意是计算宽度比率的。

        widthratio用法:{% widthratio value max_value max_width %}

        举例:

          {% widthratio 175 200 100 %} ===> 175/200 * 100 = 87.5 四舍五入 88

          {% widthratio i 1 j as product %} 别名就可以在后面引用这个变量product

        

        

          使用cycle标签来替换奇偶行变色代码

          

        as  product:

        

      方式3 :自定义filter  

      1. 构建自定义的模板 的包和模块

           在应用user下构建templateags 包,一定要有__init__.py 文件

           构建自己的filter的模块,这里起名为myfilters.py其中代码如下

          

          index.html:

          

          处理函数:

           

          记得把 应用注册一下:settings.py

          

          事实上如: 1|yesno:"y,n,none" 其实也是 类似,两种参数,1,“y,n,none”   后者切割后做处理

          自定义的filter中不能使用关键字参数

    5、用户功能设计与实现

      提供用户注册处理

      提供用户登录处理

      提供路由配置

      5.1、用户注册接口设计:

        接受用户通过Post 方法提交的注册信息,提交的数据是JSON格式的数据

        检查email 是否已存在于数据库表中,如果存在,返回错误状态吗。例如4XX,如果不存在,将用户提交的数据存入表中

        整个过程都采用AJAX 异步过程,用户提交JSON数据,服务端获取数据后处理,返回JSON

        URL:/user/reg

        METHOD: POST

      5.2、路由配置:

        为了避免项目中的urls.py 条目过多,也为了让应用自己管理自己的路由,采用多级路由。

        # blog/urls.py (项目根目录下)

          

          include 函数参数写  应用.路由模块  ,该函数就会动态导入指定的包的模块,从模块里面兑取urlpatterns,返回三元组。

          url函数第二参数如果不是可调用对象,如果是元组或列表,则会从路径中除去已经匹配的部分,将剩余部分与应用中的路由模块的urlpatterns进行匹配

        新建user/urls.py: 
          

        对user/urls.py修改如下:

            

        bolg/urls.py:

          

        user/view.py:

          

        浏览器显示:

          

          过程:

            输入127.0.0.1:8000/user/reg  先到项目根目录下的 ursl.py 的urlpatterns 匹配

            匹配到后,截断,剩下 reg/ 因为incldue 应用.模块 下,继续匹配

            直到匹配到 reg,调用reg处理函数,进行处理。

          开发过程建议使用多级。

      5.3、视图函数:

        在user/view.py中编写视图函数reg,路由做响应的调整

        测试JSON数据

          使用POST方法,提交的数类型为application/json, json 字符串要使用双引号

          这个数据是登录和注册用的,由客户端提交

           

          

        JSON数据处理:

          simplejson 比标准库方便好用,功能强大。

          $pip install simplejson 

           浏览器端提交的数据放在额请求对象的body'中,需要使用simplejson解析,解析的方式同json,但是前者更方便

          

          测试的时候,不要出现特殊字符,出现问题,会有提示:

          

        错误处理:

          注意:Http404  和 HttpResponseNotFound的区别:

             

           Django中有跟多异常类,定义在django.http下,这些类都继承自HttpResponse

           

          user/view.py 中:    

     1 from django.shortcuts import render
     2 
     3 # Create your views here.
     4 from django.http import  JsonResponse, HttpRequest,HttpResponse,HttpResponseBadRequest
     5 import simplejson
     6 from .models import User
     7 # 注册(业务) insert
     8 def reg(request:HttpRequest):
     9     try:
    10         play = simplejson.loads(request.body)
    11         print(play)
    12         email = play["email"]
    13         name = play["name"]
    14         password = play["password"]
    15         # ORM 操作
    16         user = User()
    17         user.email = email
    18         user.name = name
    19         user.password = password
    20         try: # 这个try可以去掉,不要把e法到浏览器端,
    21             # 这个try里可以做些特殊处理;
    22             user.save() # commit提交
    23             return JsonResponse({"user_id":user.id})
    24         except Exception as e:
    25             print(e)
    26             # return HttpResponseBadRequest("参数错误")
    27             raise # 直接往外抛,外面的接住。
    28     except Exception as e:
    29         print(e)
    30         return HttpResponseBadRequest("参数错误")

    6、CSRF处理

      在Post数据的时候,发现出现下面的提示:

        

      原因是:默认Django会对所有的PSOT信息做CSRF校验

        CSRF(Cross-Site request forgery)跨站请求伪造,通常缩写为CSRF 或者XSRF,是一种对网站的恶意利用。

        CSRF 则通过伪装来自受信任的用户的请求来利用受信任的网站

        CSRF 攻击往往难以防范,具有非常大的危险

          Django提供CSRF机制:

          Django第一次响应来自某个客户端的请求时会在服务器端随机生成一个token,把这个token放在cookie里,然后浏览器每次POST请求带上这个token,Django的中间件验证,这样就能避免被CSRF攻击(到View之前,有中间件,阻挡了)

       解决办法:   

          1、关闭scrf 中间件,不推荐(settings.py)

          

          2、在POST提交时,需要发送给服务器一个csrf_token

            模板中的表单Form中增加 {% csrf_token %} ,它返回到了浏览器端就会为cookie增加csrftoken字段,

            还会在表单中增加一个名为csrfmiddlewaretoken隐藏空间 <input type='hidden' name='csrfmiddlewretoken'

            value.....

            

            cookie:

            

          3、如果使用AJAX进行POST,需要在请求Header中增加X-CSRFTOKEN,其值来自cookie中获取的csrftoken值

            

            第一次请求,返回时,就会带有,所以再次请求,带上这个token就可以

    7、邮箱检查

      邮箱检查需要查user表,需要使用User类的filter方法

      email = email,前面是字段名email,后面是email变量,查询后,返回结果,如果查询有结果,则说明该email已经存在,邮箱已经注册,返回400到前端。

      测试代码:   

     1 from django.shortcuts import render
     2 
     3 # Create your views here.
     4 from django.http import  JsonResponse, HttpRequest,HttpResponse,HttpResponseBadRequest
     5 import simplejson
     6 from .models import User
     7 # 注册(业务) insert
     8 def reg(request:HttpRequest):
     9     try:
    10         play = simplejson.loads(request.body)
    11         print(play)
    12         email = play["email"]
    13         query = User.objects.filter(email=email)
    14         print(type(query),'===================') # 查询语句集
    15         ## <class 'django.db.models.query.QuerySet'> ===================
    16         # print(query.query,'===================') # 显示查询语句集
    17         if query.first():# 懒惰,只要用,就会查
    18             print('===== 查到了 =======')
    19             return HttpResponseBadRequest('用户名存在')
    20         
    21         name = play["name"]
    22         password = play["password"]
    23         # ORM 操作
    24         user = User()
    25         user.email = email
    26         user.name = name
    27         user.password = password
    28         try: # 这个try可以去掉,不要把e法到浏览器端,
    29             # 这个try里可以做些特殊处理;
    30             user.save() # commit提交
    31             return JsonResponse({"user_id":user.id})
    32         except Exception as e:
    33             print(e)
    34             # return HttpResponseBadRequest("参数错误")
    35             raise # 直接往外抛,外面的接住。
    36     except Exception as e:
    37         print(e)
    38         return HttpResponseBadRequest("参数错误")
    测试email重复

      

     

    8、用户信息存储:          

      创建User类实例,属性存储数据,最后调用save方法,Django默认是在save(),delete()的时候事务自动提交

      如果提交抛出任何错误,则捕获此异常做相应处理。

      异常处理:

      • 出现获取输入框提交信息异常,就返回异常
      • 查询邮箱存在,返回异常
      • save()方法保存数据,有异常,则向外抛出,捕获返回异常
      • 注意一点,Django的异常类继承自HttpReaponse类,所以不能raise, 只能retun

          (raise是python自己的,怎么可能raise异常到浏览器端呢)

      • 前端通过状态码判断是否成功

    9、Django日志:

      https://docs.djangoproject.com/zh-hans/2.1/topics/logging/

        settings.py:配置

     1 LOGGING = {
     2     'version': 1,
     3     'disable_existing_loggers': False,
     4     'handlers': {
     5         'console': {
     6             'class': 'logging.StreamHandler',
     7         },
     8     },
     9     'loggers': {
    10         'django.db.backends': {
    11             'handlers': ['console'],
    12             'level': 'DEBUG',
    13         },
    14     },
    15 }

        Django的日志配置在settings.py中

        必须DEBUG=True,否则logger的级别够也不打印日志

        

        配置后,控制台:

        

    10、模型操作

      管理器对象

        Django会为模型类提供一个objects对象,它是django.db.models.manager.Manager类型,用于与数据库交互。

        当定义模型类的时候,没有指定管理器,则Django会为模型类提供一个objects的管理器。

        如果在模型类型中手动指定管理器后,Django不再提供默认的objects的管理器了

        管理器是Django的模型进行数据库查询操作的接口,Django应用的每个模型都至少拥有一个管理器 

      Django ORM

        数据的校验 validation 是在对象的Save,update方法上

        

         对模型对象的CRUD, 被Django ORM 转换成相应的SQL 语句操作不同的数据源。

       查询:

         查询集

           查询会返回结果的集,它是django.db.models.query.QureySet类型

           它是惰性求值,和sqlalchemy 一样,结果就是查询的集。

           它是可迭代对象、

           1、惰性求值:

            创建查询集不会带来任何数据库的访问,知道调用方法使用数据时,才会访问数据库。在迭代,序列化,if语句中都会立即求值

           2、缓存:

            每一个查询集 都包含一个缓存,来最小化对数据库的访问。

            新建查询集,缓存为空,首次对查询集求值时,会发生数据库查询,Django会把查询的结果存在这个缓存中,并返回请求的结果,接下来对查询集求值将使用缓存的结果。

             观察下面的2个例子是要看真正生成的语句了。

             1) 没有使用缓存,每次都要去查库,查了2次

    1 [user.name for user in User.object.all()]
    2 [user.name for user in User.object.all()]

             2) 下面的语句使用了缓存,因为使用同一个结果集

    1 qs = User.object.all()
    2 [user.name for user in qs]
    3 [user.name for user in qs]

        限制查询集(切片)

          查询集对象可以直接使用索引下标的方式(不支持负索引),相当于SQL语句中的limit和offset子句

          注意使用索引返回的新的结果集,依然是惰性求值,不会立即查询(前包后不包(offset,limit))

          

          

          

       过滤器:返回查询集的方法

    名称 说明
    all() 所有
    filter() 过滤,返回满足条件的数据
    exclude() 排除,排除满足条件的数据
    order_by() 排序
    values() 返回一个对象字典的列表,列表的元素是字典,字典内是字段和值的兼职对
     1     users3 = User.objects.all() # select * from
     2     users4 = User.objects.values()
     3     print(users4,'======= 1 ============')
     4     '''
     5     <QuerySet [{'id': 2, 'name': 'jack', 'email': 'jack@qq.com', 'password': '123456'}, {'id': 3, 'name': 'jer', 'email': 'jerry@qq.con', 'password': '123456'},
     6  {'id': 4, 'name': 'tom', 'email': 'tom@qq.com', 'password': 'tom'}, {'id': 5, 'name': 'tom1', 'email': 'tom1@qq.com', 'password': 'tom1'}, {'id': 8, 'name'
     7 : 'tom2', 'email': 'tom2@qq.com', 'password': 'tom1'}, {'id': 12, 'name': 'tom3', 'email': 'tom3@qq.com', 'password': 'tom3'}]> ======= 1 ============
     8     '''
     9     print(list(users4))
    10 
    11     users5 = User.objects.filter(name='jack')
    12     print(users5,'========== 2 ==========')
    13     # <QuerySet [<User: <user2jack>]> ========== 2 ==========
    14 
    15     users6 = User.objects.exclude(name='jack')
    16     print(users6)
    17     # <QuerySet [<User: <user3jer>, <User: <user4tom>, <User: <user5tom1>, <User: <user8tom2>, <User: <user12tom3>]>
    18 
    19     users7 = User.objects.order_by()
    20     print(users7,'====')
    21     '''
    22     (0.001) SELECT `user`.`id`, `user`.`name`, `user`.`email`, `user`.`password` FROM `user` LIMIT 21; args=()
    23     <QuerySet [<User: <user2jack>, <User: <user3jer>, <User: <user4tom>, <User: <user5tom1>, <User: <user8tom2>, <User: <user12tom3>]> ====
    24     '''
    测试及结果

            filter(k1=v1).filter(k2=v2) 等价于 filter(k1=v1, k2=v2)  这是  and

            filter(pk=10)这里的pk指的是主键,不用关心字段名字,当然也可以以使用主键的字段名

          返回单个值的方法

    名称

    说明

    get() 仅返回单个满足条件的对象,如果未能返回对象则抛出DoesNotExist异常,如果能返回多条,抛出MultipleObjectsReturned异常
    count() 返回当前查询的总条数
    first() 返回第一个对象
    last() 返回最后一个对象
    exist() 判断查询集中是否有数据,如果有返回True

                

          字段查询(Field lookup)表达式
            字段查询表达式可以作为 filter() ,exclude(), get()的参数,实现where子句  

            语法:属性(字段)名称_比较运算符=值

            注意:属性名和运算符之间使用双下划线

            比较运算符如下:

    名称 举例 说明
    exact

    filter(isdelete=False)

    filter(isdeleted__exact=False)

    严格等于,可省略不写
    contains exclude(title__contains='天‘) 是否包含,大小写敏刚,等价like ‘%天%’

    startswith

    endswith

    filter(title__startswith="T") 以什么开头或结尾,大小写敏感

    isnull

    isnotnull

    filter(title__isnull=False) 是否为null

    iexact

    icontains

    istartwith

    iendwith

      i 的意思是忽略大小写
    in filter(pk__in=[1,2,3]) 是否在指定范围

    gt,gte

    lt,lte

    filter(id__gt=3)

    大于,大于等于

    小于,小于等于

    year,month,day,week_day,hour,minute,second filter(pub_date__year=2000) 对日期类型属性处理

          

          Q对象

            虽然Django提供传入条件的方式,但是不方便,filter无法实现 or,它还提供了Q对象来解决

            Q对象是Django.db.models.Q  可以使用& (and),   | (or) 操作符来组成逻辑表达式,~ 表示not

            

            可以使用 & | 和 Q 对象来构造复杂的逻辑表达式

            过滤器函数可以使用一个或多个Q对象

            如果混用关键字参数和Q对象,那么Q对象必须放在关键字参数的前面,所有参数都将and在一起

    11、注册接口设计完善

      认证:

        HTTP协议是无状态协议,为了解决它产生了cookie和session技术

      传统的session-cookie 机制

        浏览器发起第一次请求到服务器,服务器发现浏览器没有提供session id,就认为这是第一次请求,会返回一个新的session id给浏览器,浏览器只要不关闭,这个session id 就会随着每一次请求重新发给服务器端,服务器端查找这个session id,如果查到,就认为是同一个会话,如果没有查到,就认为是新的请求。

        session是会话级别的,可以在这个会话session 中创建很多数据,连接断开session清除,包括session id

        这个session id还得有过期的机制,一段时间如果没有发起请求,认为用户已经断开,就清除session。浏览器端也会清除响应的cookie信息

        服务器端保存着大量的session 信息,很消耗内存,而且如果多服务器部署,还要考虑session共享的问题。比如redis,memcached等方案

        (会话(session)和连接不能一一对应,session是证明同一个用户)

        csrf_token:解决安全

        session id:会话id

        

        

        cookie分为两种:

          持久cookie:设置了过期时间,没有过期之前,cookie信息会落地,知道过期

          会话cookie:关闭浏览器就失效

      无session方案

        既然服务器端就是需要一个ID来表示身份,name不使用session也可以创建一个ID 返回给客户端,但是要保证客户端不可篡改

        服务器生成一个表示,并使用某种算法对标识签名

        服务器收到客户端发来的标识,需要检查签名。

        这种方案的缺点是,加密,解密需要消耗CPU计算资源,无法让浏览器自己主动检查过期的数据以清除。

        这种技术成为:JWT(Json WEB Token)

      JWT:可以实现单点登录(另一台服务器登录,只需要验证就行)

        优点:用来保持状态,同时解决了cookie可以改变的问题,还有解决了session存储,共享的问题,因为本身不需要session了

        不能解决身份伪造

        JWT:是一种采用json方案安装传输信息的方式

        这次使用PyJWT,它是python对JWT的实现。

        文档:https://pyjwt.readthedocs.io/en/latest/

        安装:$ pip install pyjwt

        JWT原理:     

     1 import jwt
     2 
     3 key = "seret"
     4 
     5 token = jwt.encode({'a':"a"}, key, "HS256")
     6 print(token)
     7 # b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoiYSJ9._sX8rOc7ZDAxDInF4sh53zjv__PQO4-pAcDdR9pXYN8'
     8 print(jwt.decode(token, key, algorithms=['HS256']), type(jwt.decode(token, key, algorithms=['HS256'])))
     9 # {'a': 'a'} <class 'dict'>
    10 
    11 header, payload, signature = token.split(b'.')
    12 print(header)
    13 print(payload)
    14 print(signature)
    15 
    16 import  base64
    17 def addeq(b:bytes):
    18     ''' 为 base64 编码补齐等号'''
    19     rest = 4 - len(b) % 4
    20     return  b + b'=' * rest
    21 
    22 print('header=', base64.urlsafe_b64decode(addeq(header)))
    23 # header= b'{"typ":"JWT","alg":"HS256"}'
    24 print('payload=', base64.urlsafe_b64decode(addeq(payload)))
    25 # payload= b'{"a":"a"}'
    26 print('signature=', base64.urlsafe_b64decode(addeq(signature)))
    27 # signature= b'xfexc5xfcxacxe7;d01x0cx89xc5xe2xc8yxdf8xefxffxf3xd0;x8fxa9x01xc0xddGxdaW`xdf'
    28 
    29 # 根据jwt算法,重新生成签名
    30 # 1 获取算法对象
    31 from jwt import algorithms
    32 alg =  algorithms.get_default_algorithms()['HS256']
    33 newkey = alg.prepare_key(key)
    34 print(newkey)# b'seret'
    35 
    36 # 2、获取前两部分 header payload
    37 signing_inpur, _ , _ = token.rpartition(b'.')
    38 print(signing_inpur)
    39 
    40 # 3、使用key 签名
    41 signature = alg.sign(signing_inpur, newkey)
    42 
    43 print('=======================================')
    44 print(signature)
    45 print(base64.urlsafe_b64encode(signature))

           结果:

     2 b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoiYSJ9._sX8rOc7ZDAxDInF4sh53zjv__PQO4-pAcDdR9pXYN8'
     3 {'a': 'a'} <class 'dict'>
     4 b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
     5 b'eyJhIjoiYSJ9'
     6 b'_sX8rOc7ZDAxDInF4sh53zjv__PQO4-pAcDdR9pXYN8'
     7 header= b'{"typ":"JWT","alg":"HS256"}'
     8 payload= b'{"a":"a"}'
     9 signature= b'xfexc5xfcxacxe7;d01x0cx89xc5xe2xc8yxdf8xefxffxf3xd0;x8fxa9x01xc0xddGxdaW`xdf'
    10 b'seret'
    11 b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoiYSJ9'
    12 =======================================
    13 b'xfexc5xfcxacxe7;d01x0cx89xc5xe2xc8yxdf8xefxffxf3xd0;x8fxa9x01xc0xddGxdaW`xdf'
    14 b'_sX8rOc7ZDAxDInF4sh53zjv__PQO4-pAcDdR9pXYN8='
    15 
    16 Process finished with exit code 0

         由此,可知JWT 生成的token分为 三部分

      1. header,由数据类型,加密算法构成
      2. payload, 负载要传输的数据,一般来说,放入Python对象即可,会被json序列化
      3. signature,签名部分,是前面2部分数据分别base64 编码后使用点号连接后,利用加密算法使用(key + 前两部分)计算好一个结果,在被base64 编码,得到签名

         所有数据都是明文传输的,只是做了base64 编码,如果是敏感信息,请不要使用jwt

        数据签名的目的不是为了隐藏数据,而是保证数据不被篡改,如果数据篡改,发回服务器,服务器使用自己的key在计算以便,然后进行签名比对,一定对不上签名。

         应用场景:

          认证:这是jwt 最常用的场景,一旦用户登录成功,就会得到jwt,然后请求中就可以带上这个jwt,服务器中jwt验证通过,就可以允许访问资源,甚至可以在不同域名中传递,在单点登录(single sign on)中应用广泛,

          数据交换:jwt可以防止数据被篡改,他可以使用公钥,私钥,确保请求的发送者是可信的。

           

     12、密码:

      使用邮箱 +密码登录方式

      邮箱要求唯一就行。密码如何存储?

       ---: MD5存储,但是目前也不安全,网上很多MD5的网站,使用反查能找到密码

       ---:加盐,使用 hash(password +salt)的结果存入数据库中,就算拿到数据库的密码反查,也没有用了,如果是固定加盐,还是容易被找到规律的,或者从原码中泄露,随机加盐,每一次盐都变,就增加了破解难度。

      暴力破解:什么密码都可能被暴力破解,所以,使用慢hash算法,例如 bcrypt,就会让每一次计算都很慢,都是秒级别的,这样穷举来破解化的时间会很长。

      bctypt 

        安装:pip install bcrypt

        测试:    

     1 import bcrypt
     2 import datetime
     3 
     4 password = b'123456'
     5 
     6 # 每次拿到的盐不一样
     7 print(1, bcrypt.gensalt())
     8 print(2, bcrypt.gensalt())
     9 
    10 salt = bcrypt.gensalt()
    11 # 拿到的盐相同,计算的到的密文相同
    12 x = bcrypt.hashpw(password, salt)
    13 print(3, x)
    14 x = bcrypt.hashpw(password, salt)
    15 print(4, x)
    16 
    17 # 每次拿到的盐不同,计算生成的密文也不一样
    18 x = bcrypt.hashpw(password, bcrypt.gensalt())
    19 print(5, x)
    20 x = bcrypt.hashpw(password, bcrypt.gensalt())
    21 print(6, x)
    22 print('====' * 20)
    23 # 校验
    24 print(bcrypt.checkpw(password, x), len(x))
    25 print(bcrypt.checkpw(password+b'  ', x), len(x))
    26 
    27 # 计算时长
    28 start = datetime.datetime.now()
    29 y = bcrypt.hashpw(password, bcrypt.gensalt())
    30 delta = (datetime.datetime.now() - start).total_seconds()
    31 print(y)
    32 print(10, delta)
    33 
    34 # 检验时长
    35 start = datetime.datetime.now()
    36 y = bcrypt.checkpw(password, x)
    37 delta = (datetime.datetime.now() - start).total_seconds()
    38 print(y,'====')
    39 print(11, delta)
    40 
    41 
    42 start = datetime.datetime.now()
    43 y = bcrypt.checkpw(b'1', x)
    44 delta = (datetime.datetime.now() - start).total_seconds()
    45 print(y)
    46 print(12, delta)

        结果:

     1 F:pyenvslog2Scriptspython.exe F:/Python项目/blog12/test.py
     2 1 b'$2b$12$lNtU.U.dpmTIAaQjWLnF5.'
     3 2 b'$2b$12$Vo0ulFEzzing3kM6fXTu2u'
     4 3 b'$2b$12$Nr/Y2w9zyyJtnUqrqW6SCuXRM8PlDxDr9HZM3zViRdEGjOWt5UyvG'
     5 4 b'$2b$12$Nr/Y2w9zyyJtnUqrqW6SCuXRM8PlDxDr9HZM3zViRdEGjOWt5UyvG'
     6 5 b'$2b$12$3XXyg.K9HyI.p24DBEYGi.QdEQobiAgM0i3mz7FUrjoyLRjl6./ou'
     7 6 b'$2b$12$Zoq1fNKR.x70wXQBQZpii.tTk5.FMDnJp7fMIRpPGAYZZO6uA6ajK'
     8 ================================================================================
     9 True 60
    10 False 60
    11 b'$2b$12$MwXbhhMvDqLXr5eySavOwu2oQm1dQAZeivj/kKDD27GGSYxj1qrSa'
    12 10 0.34502  可以看到时间是很长的13 True ====
    14 11 0.337019
    15 False
    16 12 0.331019

        从耗时看出,bcrypt 加密,验证非常耗时,所以穷举,非常耗时,而且碰巧攻破一个密码,由于盐不一样,还得穷举另一个

        

    13、注册代码  更新

      全局变量

      项目settings.py文件实际上就是全局变量的配置文件

      SECCRET_KEY 一个强密码

      使用jwt 和bcrypt  

     1 from django.shortcuts import render
     2 
     3 # Create your views here.
     4 from django.http import  JsonResponse, HttpRequest,HttpResponse,HttpResponseBadRequest
     5 import simplejson
     6 from .models import User
     7 from django.db.models.manager import Manager
     8 from django.db.models import Q
     9 import  jwt
    10 import bcrypt
    11 import datetime
    12 from django.conf import settings
    13 KEY = settings.SECRET_KEY
    14 print(KEY)
    15 # SECRET_KEY = ')enlwt1x02$&&egj-q&%=-jq2(*4iy^o$le2te@x2inp&bq_7)' ----在blog/settings.py中
    16 
    17 # 服务器端生成一个 token
    18 def gen_token(user_id):
    19     return  jwt.encode({
    20         'user_id':user_id,
    21         "timestamp":int(datetime.datetime.now().timestamp()) # 要取整
    22     },KEY, 'HS256').decode() # 字符串
    23 
    24 # 注册(业务) insert
    25 def reg(request:HttpRequest):
    26     try:
    27         play = simplejson.loads(request.body)
    28         # print(play, type(play),'========================')
    29         email = play["email"]
    30         a = User.objects.filter(email=email)
    31         # print(a.query) # 查看查询语句
    32         if a.first():# 懒惰,只要用,就会查
    33             return HttpResponseBadRequest('用户名存在')
    34 
    35         name = play["name"]
    36         # 这两步可以合起来,减少计算
    37         password = play["password"]
    38         print(password)
    39         password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) ------保证每次的盐都不同,提高安全性
    40         # ORM 操作
    41         user = User()
    42         user.email = email
    43         user.name = name
    44         user.password = password
    45 
    46         try: # 这个try可以去掉,不要把e法到浏览器端,
    47             # 这个try里可以做些特殊处理;
    48             user.save() # commit提交
    49             # 如果正常,返回json数据
    50             return JsonResponse({"token":gen_token(user.id)})
    51         except Exception as e:
    52             print(e)
    53             # return HttpResponseBadRequest("参数错误")
    54             raise # 直接往外抛,外面的接住。
    55     except Exception as e: # 有任何异常返回。
    56         print(e)
    57         return HttpResponseBadRequest("参数错误") # 这里返回实例,这不是异常类,继承自httpresponse
    58 
    59 def show(request:HttpRequest):
    60     ################################################################################
    61     #Q对象
    62     return JsonResponse({})

           

  • 相关阅读:
    07: mysql锁和事物隔离
    06: mysql索引查找原理及调优
    06: 字典、顺序表、列表、hash树 实现原理
    05:树结构
    02:MongoDB操作
    01:MongoDB基础
    02: CMDB设计思路
    二级制包安装Tomcat 与 RPM包安装Tomcat
    Docker的volume机制实现容器数据的持久性存储
    配置docker的私有仓库
  • 原文地址:https://www.cnblogs.com/JerryZao/p/10009367.html
Copyright © 2011-2022 走看看