多人博客项目(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({})

       

原文地址:https://www.cnblogs.com/JerryZao/p/10009367.html