Django高级应用
Cookie
Cookie的原理
- Cookie是由服务器产生,存储在浏览器中的
键值对
数据 - 每个域名的Cookie都相对独立
- 浏览器访问域名为A的erl地址,会把A域名下的Cookie一起传到服务器
- Cookie可以设置过期时间
Cookie的设置和获取
cookie的设置:
response = HttpResponse()
response.set_cookie([key],[value],max_age=60*60)
return response
注:max_age:默认为none(关闭浏览器则删除Cookie),单位秒:60*60表示3600秒即一个小时
取cookie:
value = request.COOKIES.get([key],None)
url:
urlpatterns = [
path('',views.index_handler,name='index'),
re_path('set_cookie/(.+)/(.+)',views.set_cookie_handeler,name='set_cookie'),
# 正则传递两个参数,前一个是key,后一个是value
re_path('get_cookie/(.+)',views.get_cookie_handler,name='get_cookie'),
]
view:
def index_handler(request):
return HttpResponse('index')
def set_cookie_handeler(request,key,value):
response = HttpResponse()
response.set_cookie(key, value, max_age=60 * 60)
return response
def get_cookie_handler(request,key):
value = request.COOKIES.get(key)
return HttpResponse(value)
Cookie的安全性
- Cookie存储在客户端,即浏览器,所以具有不安全性
- 对于敏感的数据,应该加密,或者保存在服务端
Session
Session的原理
- Session基于Cookie
- Session将敏感的数据以加密的方式保存在服务器
- 原理:浏览器访问服务器,发送用户名密码等私密数据,服务器将私密数据加密、过期时间保存进数据库并返回一个sessionID,在浏览器下一次访问时,会向服务器发送sessionID,服务器接收到ID后到数据库比对拿到加密后的私密数据并解密。
Session 的设置和获取
设置:
request.session[key] = value
设置过期时间:
request.session.set_expiry(60*60)
获取:
value = request.session.get(key) # 如果没有这个key则返回None
首先设置数据库,在项目setting中设置:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'localhost',
'PORT':'3306',
'NAME':'django3',
'USER':'root',
'PASSWORD':'123456',
}
}
url:
urlpatterns = [
re_path('set_session/(.+)/(.+)', views.set_session_handeler, name='set_session'),
re_path('get_session/(.+)', views.get_session_handler, name='get_session'),
]
view:
def set_session_handeler(request,key,value):
request.session[key] = value
return HttpResponse('设置成功')
def get_session_handler(request,key):
value = request.session.get(key)
return HttpResponse(value)
flush()和clear()命令
- request.session.flush()删除表数据
- request.session.clear()清空sessionID对应的数据
Django连接Redis服务
Redis特点
- Redis是一种非关系型数据库
- 数据读写速度快
- 可以存储的数据量小
Redis服务环境搭建
- 安装Redis(环境变量)
- 安装包:
pip install redis
pip install django-redis
pip install django-redis-sessions
Settings配置
SESSION_ENGINE = 'redis_sessions.session' # 选择Redis存储session
SESSION_REDIS_HOST = 'localhost' # Redis主机地址
SESSION_REDIS_PORT = '6379' # redis端口号
SESSION_REDIS_DB = 0 # 数据库编号:0-11
SESSION_REDIS_PASSWORD = '' # 登录密码
表单数据的提交和接收
表单编写
form 标签
提交地址:action
提交方法:method
提交文件:enctype="multipart/form-data"
如:
<form action="www.edu.csdn.net"method="post"enctype="multipart/form-data">
input:
type = "text" 文本输入框
type = "password" 密码输入框
type = "radio" 单选框
type = "checkbox" 复选框
type = "file" 文件框
type = "button" 按钮
type = "submit" 提交
type = "reset" 重置
下拉框:
<select>
<option>1</option>
<option>2</option>
</select>
大文本:
<textarea cols="列数" rows="行数"></textarea>
GET\POST请求方式
- GET请求提交的数据参数放在url中
- POST请求提交的数据被加密,url中无法看到
后台数据获取
GET:
value = request.GET.get([key],[默认值])
key不存在以默认值进行填充
values = request.GET.getlist([key])
POST:
value = request.POST.get([key],[默认值])
values = request.POST.getlist([key])
注意:post请求,需要在表单中加入{%csrf_token%},或者取消csrf中间件
CSRF跨域攻击
CSRF攻击原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t1AqDKEN-1603422469011)(D:\git笔记\徐仓鼠的学习笔记\python\CSRF攻击.png)]
- 首先用户C浏览并登录了受信任站点A;
- 登录信息验证通过以后,站点A会在返回给浏览器的信息中带上已登录的cookie,cookie信息会在浏览器端保存一定时间(根据服务端设置而定);
- 完成这一步以后,用户在没有登出(清除站点A的cookie)站点A的情况下,访问恶意站点B;
- 这时恶意站点 B的某个页面向站点A发起请求,而这个请求会带上浏览器端所保存的站点A的cookie;
- 站点A根据请求所带的cookie,判断此请求为用户C所发送的。
因此,站点A会报据用户C的权限来处理恶意站点B所发起的请求,而这个请求可能以用户C的身份发送 邮件、短信、消息,以及进行转账支付等操作,这样恶意站点B就达到了伪造用户C请求站点 A的目的。
预防CSRF攻击的方法
-
尽量使用POST,限制GET
GET接口太容易被拿来做CSRF攻击。接口最好限制为POST使用,GET则无效,降低攻击风险。
当然POST并不是万无一失,攻击者只要构造一个form就可以,但需要在第三方页面做,这样就增加暴露的可能性。 -
将cookie设置为HttpOnly
CRSF攻击很大程度上是利用了浏览器的cookie,为了防止站内的XSS漏洞盗取cookie,需要在cookie中设置“HttpOnly”属性,这样通过程序(如JavaScript脚本、Applet等)就无法读取到cookie信息,避免了攻击者伪造cookie的情况出现。
在Java的Servlet的API中设置cookie为HttpOnly的代码如下:response.setHeader( "Set-Cookie", "cookiename=cookievalue;HttpOnly")
-
增加token
CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于cookie中,因此攻击者可以在不知道用户验证信息的情况下直接利用用户的cookie来通过安全验证。由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信总不存在于cookie之中。鉴于此,系统开发人员可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务端进行token校验,如果请求中没有token或者token内容不正确,则认为是CSRF攻击而拒绝该请求。
假设请求通过POST方式提交,则可以在相应的表单中增加一个隐藏域:<input type="hidden" name="_toicen" value="tokenvalue"/>
token的值通过服务端生成,表单提交后token的值通过POST请求与参数一同带到服务端,每次会话可以使用相同的token,会话过期,则token失效,攻击者因无法获取到token,也就无法伪造请求。
在session中添加token的实现代码:HttpSession session = request.getSession(); Object token = session.getAttribute("_token"); if(token == null I I "".equals(token)) { session.setAttribute("_token", UUID.randomUUIDO .toString()); }
-
通过Referer识别
根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。在通常情况下,访问一个安全受限的页面的请求都来自于同一个网站。比如某银行的转账是通过用户访问 http://www.xxx.com/transfer.do 页面完成的,用户必须先登录 www.xxx.com ,然后通过单击页面上的提交按钮来触发转账事件。当用户提交请求时,该转账请求的Referer值就会是提交按钮所在页面的URL(本例为 www.xxx.com/transfer.do )。如果攻击者要对银行网站实施CSRF攻击,他只能在其他网站构造请求,当用户通过其他网站发送请求到银行时,该请求的Referer的值是其他网站的地址,而不是银行转账页面的地址。因此,要防御CSRF攻击,银行网站只需要对于每一个转账请求验证其Referer值即可,如果是以www.xx.om域名开头的地址,则说明该请求是来自银行网站自己的请求,是合法的;如果Referer是其他网站,就有可能是CSRF攻击,则拒绝该请求。
取得HTTP请求Referer:String referer = request.getHeader("Referer");
模型类多表操作
模型类一对多、多对多
-
外键种类
关键字 说明 ForeignKey 一对多 ManytoManyField 多对多 OneToOneField 一对一 -
外键的常用参数
Django语法
关键字 说明 to 引用的模型类(自关联"self") on_delete 外键约束:
【1】models.CSDCADE 级联操作(多对一,一的一方删除,多的一方也删除
【2】models.PROTECT 报异常(被引用的一方不能被删除)
【3】models.SET_NULL 设置为null(多对一,一的一方删除,多的一方外键为null)
【4】models.DO_NOTHING 什么也不做related_name 反向引用,如果两个表间有多种外键关系,需要指明related_name如果指向,默认是[模型类小写]_set MYSQL语法
级联类型 解释 on delete restrict 默认值,抛异常 on delete cascade 如果主表被引用的外键删除,相关联的表的记录也会被删除 on delete set null 如果主表被引用的外键删除,相关联的表的外键设置为空 on delete no action 什么也不做
(1) 模型表中的一对多操作
User2表结构:username
News表结构:topic,sender外键指向User2的ID
当执行完:
user = User2()
user.username='user2'
user.save()
news=News()
news.topic = 'topic2'
news.sender = user
news.save()
后:
news.sender
# 显示结果<User2: User2 object (ID值)>
news.sender.username
# 显示结果'user2'
user.news_set 这就是反向引用
# 显示结果 <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x0000000003B75EC8>
user.news_set.all()
# 显示结果<QuerySet [<News: News object (2)>]>
user.news_set.all()[0]
#显示结果<News: News object (2)>
user.news_set.all()[0].topic
# 显示结果'topic2'
(2) 模型表中的多对多操作
class User2(models.Model):
username = models.CharField(max_length=16)
# 一个用户可以收藏多个帖子,一个帖子可被多个用户收藏
class News(models.Model):
topic = models.CharField(max_length=100)
# 一个用户可以发送多个帖子,一个帖子只能由一个用户发送,即(一)News对(多)User2
sender = models.ForeignKey(to=User2,on_delete=models.CASCADE,related_name='sender_set')# 一个用户被删除,那么他发的所有帖子都会被删除
collectors = models.ManyToManyField(to=User2,related_name='collectors_set')
# 此时出现一个问题,原先user查看发送的帖子是user.news_set,
# 但是现在有两层对应关系,所以再用news_set就不行了,
# 那么怎么区分发的帖子和收藏的帖子?加入参数related_name
交互模式中,添加收藏帖子有两种操作
1.user.collectors_set.add(news)
2.news.collectors.add(user)
查看user收藏的帖子:user.collectors_set.all()
查看帖子被哪些user收藏:news.collectors.all()
(3) 模型表中的自关联
class Area(models.Model):
name = models.CharField(max_length=10)
pArea = models.ForeignKey(to='self',null=True,on_delete=models.CASCADE)
area = Area
area.name='beijing'
area.save()
area2 = Area
area2.name='chaoyang'
area2.pArea=area 朝阳的父级地区为北京
area2.save()
area.area_set.all() 查看北京子地区都有哪些
area2.pArea 查看朝阳的父级地区
中间件Django Middelware应用
中间件的原理
Django框架原理:浏览器发送请求到服务器,服务器根据路由映射path将请求对接到对应的处理器,由处理器具体处理后再返回响应
中间件原理:在浏览器发送请求到服务器和服务器返回响应时可以进行拦截,并加以修饰
中间件的使用
- 定义中间件
class middleware(MiddlewareMixin):
def __init__(self,get_response=None):
super().__init__(get_response)
# 初始化中间件
print('init_middleware')
def process_request(self, request):
# 没有return或return None 继续调用其他视图
# return response 则直接返回响应
print('process_request')
def process_response(self, request, response):
# 必须return response
print('process_response')
return response
- 在settings中配置中间件
MIDDLEWARE = [‘middleware.middleware’] 类名.方法名
est(self, request):
# 没有return或return None 继续调用其他视图
# return response 则直接返回响应
print(‘process_request’)
def process_response(self, request, response):
# 必须return response
print(‘process_response’)
return response
2. 在settings中配置中间件
MIDDLEWARE = ['middleware.middleware'] 类名.方法名