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,遵循以下顺序:
-
- 字典查找,例如foo[" bar" ],把foo当作字典,bar 当作key
- 属性或方法的查找,例如 foo.bar ,把foo当作对象,bar 当作属性或方法
- 数字索引查找,例如 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
-
- 构建自定义的模板 的包和模块
在应用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("参数错误")
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'\xfe\xc5\xfc\xac\xe7;d01\x0c\x89\xc5\xe2\xc8y\xdf8\xef\xff\xf3\xd0;\x8f\xa9\x01\xc0\xddG\xdaW`\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'\xfe\xc5\xfc\xac\xe7;d01\x0c\x89\xc5\xe2\xc8y\xdf8\xef\xff\xf3\xd0;\x8f\xa9\x01\xc0\xddG\xdaW`\xdf' 10 b'seret' 11 b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoiYSJ9' 12 ======================================= 13 b'\xfe\xc5\xfc\xac\xe7;d01\x0c\x89\xc5\xe2\xc8y\xdf8\xef\xff\xf3\xd0;\x8f\xa9\x01\xc0\xddG\xdaW`\xdf' 14 b'_sX8rOc7ZDAxDInF4sh53zjv__PQO4-pAcDdR9pXYN8=' 15 16 Process finished with exit code 0
由此,可知JWT 生成的token分为 三部分
-
- header,由数据类型,加密算法构成
- payload, 负载要传输的数据,一般来说,放入Python对象即可,会被json序列化
- 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:\pyenvs\blog2\Scripts\python.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({})