24.Django(crsfctoken工作原理、ajax解决csrftoken的三种方式、跨域、modelform组件)
-
CRSF
跨站请求伪造,浏览器以及网页一般是不允许跨站i请求的,CSRF也是黑客一般盗取网站信息或者用户资料的一种手段。
-
当你请求页面,如果页面源码中没有
{% csrf_token %}标签,那么你的cookie俩民就不会有csrftoken:随机加密字符串的键值对,当你访问页面时,由于页面的form表单中有{% csrf_token %}标签,则你的cookie里面就会添加一个crsftoken:随机字符串的键值对。![]()
cookie中除去存在这个键值对(
csrftoken:随机加密字符串),还存有session_id:随机字符串,而这个session_id的键值对一般用于身份认证的。
csrf的工作原理:如果csrf攻击一个网站,那么这个网站一般没有做防跨站请求伪造的功能(相当于你的Django的csrf验证的中间件注销,那么就没有这个验证功能了),这种网站存有跨站请求伪造的漏洞。
![]()
用户,登录网站A成功之后在浏览器中的cookie存储了用户用于身份认证的
session_id:随机字符串然后用户有登陆了一个不信任的网站B,此网站B,让你跳转到网站A,浏览器带着你之前的cookie访问了网站A,那么不信任的网站B,相当于利用了用户的session_id身份认证骗过了网站A,从而进行用户权限级别的操作。上面的过程前题:网站A 中没有做防CSRF(跨域请求伪造)的处理。
-
如果处理CSRF跨站请求伪造?
这就用到了我们之前学的csrftoken,csrftoken就是处理CSRF跨站i请求伪造的一种机制。
Django框架给我们提供的csrfmiddleware中间件,就是处理CSRF跨站请求伪造的一种机制。我们之前将的csrftoken的工作原理不是 最根本的原理,不全面,接下来,我们将真正的在scrfmiddleware中间件是如何验证你的csrftoken这个加密字符串的。
这个我们Django是怎么验证的?
当你请求页面,如果页面源码中没有
{% csrf_token %}标签,那么你的cookie俩民就不会有csrftoken:随机加密字符串的键值对,当你访问页面时,由于页面的form表单中有{% csrf_token %}标签,则你的cookie里面就会添加一个crsftoken:随机字符串的键值对。![]()
cookie中除去存有这个键值对(csrftoken键值对),还存有
session_id:随机字符串,而这个session_id的键值对一般是用于身份认证的。你的隐藏的input标签每次刷新页面,他的随机的加密字符串都会发生变化,而cookie里面的scrftoken:加密字符串 不会发生变化。
Django框架不是所有的请求都会给你设置csrftoken防跨站请求伪造的处理,
1、你在页面源码写上
{% csrf_token %},Django就会给你强制的进行csrftoken认证2、post、put、head只要是这些i请求,Django就会给你强制的及进行csrftoken认证
-
csrftoken最完整的工作原理可以在你简历上著名:源码研究。
他是利用cookie的csrftoken的加密字符串去校验你的隐藏的input的随机加密字符串。这就得看源码了:
![]()
token字符串的前32位是salt,后面是加密后的token,通过salt能解密出唯一的secret。Django会验证表单中的token和cookie中的token是否能解出同样的secret,secret一样则本次请求合法。
![]()
ajax解决csrftoken的三种方式
我们之前讲了ajax进行csrftoken认证的两种方式。
启动一个流程:
url(r'^test/', views.test, name='test'),
def test(request):
if request.method == 'GET':
return render(request,'test.html')
else:
print(request.POST.get('username'))
return HttpResponse('登录成功')

我们在app01的应用下,创建一个templates也是可以找到对应的html文件的。
Django配置html文件以及静态文件不止是我们之前讲的一种方式,还有很多。

第三种方式:
我们的cookie里面存有一个csrftoken的键值对,我通过ajax获取到这个值,封装到ajax的请求头种,这样我也可以通过验证。
如歌只是借助于jQuery.js 你无法在前端操作cookie的数据,我们还得需要一个插件:
jQuery.cookie.js插件,这样我们就可以利用jQuery操作cookie的值了。


{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap 101 Template</title>
</head>
<body>
<h1>你好,test!</h1>
⽤户名:<input type="text" id="user">
<button id="btn">提交</button>
<script src="{% static 'jQuery.js' %}"></script>
<script src="{% static 'jquery.cookie.js' %}"></script>
<script>
$('#btn').click(function () {
$.ajax({
headers:{
'X-CSRFToken': $.cookie('csrftoken'),
},
url: "{% url 'test' %}",
type: "post",
data:{
username: $('#user').val(),
},
success: function (res) {
console.log(res)
}
})
})
</script>
</body>
</html>
跨域
概念
-
同源策略:
同源:协议(http)、域名、端口相同的。实际就是协议+ip+端口相同,(也就是一个网站内的所有的页面之前的通信)。同源策略:是一种约定,它是浏览器最核心也是最基本的安全功能,只要你是同源的前提下通信,浏览器是支持的。非同源的两个网站之间默认不允许互相通信
测试
-
启动两个项目(pro1、pro2)
![]()
![]()
-
修改pro2的端口号为8001

-
完善pro2项目
urls:
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index, name='index'),
]
views函数:
from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.
def index(request):
dic = {'name': 'pro02项目'},
return JsonResponse(dic) -
完善pro1项目
js文件放在这里settings就不用配置了,可以直接使用
![]()
urls:
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index, name='index'),
url(r'^base/', views.base, name='base'),
]
views:
from django.shortcuts import render
from django.http import JsonResponse
def index(request):
dic = {'name': 'pro01项目'}
return JsonResponse(dic)
def base(request):
return render(request, 'base.html')
base.html:
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>你好,pro1项目的base</h1>
<button id="btn">走你</button>
<script src="{% static 'jquery-3.5.1.js' %}"></script>
<script>
$('#btn').click(function () {
$.ajax({
url: '{% url "index" %}',
type: 'get',
success: function (res) {
console.log(res)
}
})
})
</script>
</body>
</html>
-
让pro1的ajax请求访问pro2的index
{% load static %}![]()
研究一下报错原因:
8000的请求已经请求到了8001的数据,但是被浏览器阻挡的,头部缺少一个
Access-Control-Allow-Origin的配置。上面这种情况不满足同源策略,其实就叫跨域请求
解决:在pro2项目的index中加入
from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.
def index(request):
dic = {'name': 'pro02项目'}
ret = JsonResponse(dic)
ret['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000'
return ret![]()
那么问题来了:如果一个网站要是支持多个其他网站访问怎么办?
# 允许多个的,在字符串里用逗号隔开
def index(request):
dic = {'name': 'pro02项目'}
ret = JsonResponse(dic)
ret['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000, http://127.0.0.1:8002, ' # 允许多个
return ret
# 允许所有的,直接等于*即可,(不安全)
def index(request):
dic = {'name': 'pro02项目'}
ret = JsonResponse(dic)
ret['Access-Control-Allow-Origin'] = '*' # 支持所有
return ret
CORS的请求分类
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
-
区分简单请求与非简单请求
条件(1)与条件(2)全部满足才是简单请求。
(1)请求方法是一下三种方法之一:(也就是说如果你的请求方法是神农put、delete等肯定是非简单请求)
HEAD
GET
POST
(2)HTTP的头信息不超出一下几种字段:(如果比这些请求多,那么一定是非简单请求)
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限与三个值application/x-www-form-urlencoded、
multipart/form-data、text/plain,也就是说,如果你发送的application/json格式
的数据,那么肯定是⾮简单请求,vue的axios默认的请求体信息格式是json的,ajax默认是
urlencoded的。 -
简单请求与非简单请求的区别
简单请求与非简单请求的区别? 简单请求:一次请求 非简单请求:两次请求,在发送数据之前会先发一次请求用于做预检(OPTIONS),只有预检通过后才再次发送一次请求用于数据传输。 关于"预检" - 请求方式:OPTIONS - "预检"其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息 - 如何“预检”: => 如果复杂请求时PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过 Access-Control-Request-Method => 如果复杂请求设置了请求头,则服务选都需要设置允许某请求头,否则“预检”不通过 Access-Control-Request-Headers
测试⼀个⾮简单请求:
更改pro1的ajax:
$('#btn').click(function () {
$.ajax({
{#url: '{% url "index" %}',#}
url: "http://127.0.0.1:8001/index/",
type: 'get',
data: JSON.stringify({'k1': 'v1'}),
success: function (res) {
console.log(res)
}
})
})
查看请求流程:


针对与这种非简单请求,我们还要封装一个头部。(Access-Control-Allow-Headers)
pro2 index函数:
from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.
def index(request):
dic = {'name': 'pro02项目'}
ret = JsonResponse(dic)
ret['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000'
ret['Access-Control-Allow-Headers'] = 'content-type'
return ret

modelform
-
引子
form组件虽然功能强大,但是使用起来比较麻烦,写的代码比较多
![]()
这些字段,以及里面配置的属性,我们在自己创建bookdorm组件中,都重新写一遍
![]()
有没有一种机制,直接将models对应的某张表的所有字段转换类似与form组件中的所有字段,而且不用我再重新写,并且还具有form组件功能?当然有:modelsform组件
生成标签
我们再增加书籍的页面去测试:
class BookModelForm(forms.ModelForm):
class Meta:
model = models.Book # 字段来自于 book表
fields = '__all__' # book表中的所有字段都拿过来

{% for obj in model_form_obj %}
<div class="form-group">
<label for="{{ obj.id_for_label }}">{{ obj.label }}</label>
{{ obj }}
<span style="color: red; font-size: 14px">{{ obj.errors.0 }}</span>
</div>
{% endfor %}


book表中的字段属性限制:

这些限制时数据再写入数据库中的限制,这些限制同样可以转化成我们的modelform的限制。
给所有标签设置bootstrap样式:
# 和form组件一样增加一个__init__函数
class BookModelForm(forms.ModelForm):
class Meta:
model = models.Book # 字段来自于 book表
fields = '__all__' # book表中的所有字段都拿过来
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(self.fields)
for obj in self.fields.values():
obj.widget.attrs.update({'class': 'form-control'})

modelform组件单独给字段设置样式,优先级最高。
class BookModelForm(forms.ModelForm):
publishDate = forms.DateField(
widget=forms.widgets.TextInput(attrs={'type': 'date'},)
)
class Meta:
model = models.Book # 字段来自于 book表
fields = '__all__' # book表中的所有字段都拿过来
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(self.fields)
for obj in self.fields.values():
obj.widget.attrs.update({'class': 'form-control'})


更改labels标题为中文:
class BookModelForm(forms.ModelForm):
publishDate = forms.DateField(
widget=forms.widgets.TextInput(attrs={'type': 'date'},)
)
class Meta:
model = models.Book # 字段来自于 book表
fields = '__all__' # book表中的所有字段都拿过来
labels = {
'title': '书籍名称:',
'publishDate': '出版日期:',
'price': '价格:',
'publish': '出版社:',
'author': '作者:',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(self.fields)
for obj in self.fields.values():
obj.widget.attrs.update({'class': 'form-control'})


校验数据
可以使用局部钩子
class BookModelForm(forms.ModelForm):
publishDate = forms.DateField(
label='出版日期:',
widget=forms.widgets.TextInput(attrs={'type': 'date'},)
)
class Meta:
model = models.Book # 字段来自于 book表
fields = '__all__' # book表中的所有字段都拿过来
labels = {
'title': '书籍名称:',
'publishDate': '出版日期:',
'price': '价格:',
'publish': '出版社:',
'author': '作者:',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(self.fields)
for obj in self.fields.values():
obj.widget.attrs.update({'class': 'form-control'})
# 局部钩⼦
def clean_title(self):
value = self.cleaned_data['title']
if 'xx' in value:
raise ValidationError('不能出现敏感词:xx')
else:
return value
保存数据
def add(request):
model_form_obj = BookModelForm()
if request.method == 'GET':
return render(request, 'add.html', {'model_form_obj': model_form_obj})
else:
model_form_obj = BookModelForm(request.POST)
if model_form_obj.is_valid():
# 利用 modelform组件将数据写入数据库
model_form_obj.save()
return redirect('home')
else:
return render(request, 'add.html', {'form_obj': form_obj})
更新数据
在model_form_obj = BookModelForm(request.POST)的request.POST后面加上instance=book_obj。
def edit_book(request, book_id):
book_obj = models.Book.objects.get(id=book_id)
if request.method == 'GET':
model_form_obj = BookModelForm(instance=book_obj)
return render(request, 'edit_book.html', {'model_form_obj': model_form_obj})
else:
model_form_obj = BookModelForm(request.POST, instance=book_obj)
if model_form_obj.is_valid():
model_form_obj.save()
return redirect('show_books')
else:
return render(request, 'edit_book.html', {'model_form_obj': model_form_obj})













浙公网安备 33010602011771号