24.Django(crsfctoken工作原理、ajax解决csrftoken的三种方式、跨域、modelform组件)

csrftoken工作原理

  • CRSF

    跨站请求伪造,浏览器以及网页一般是不允许跨站i请求的,CSRF也是黑客一般盗取网站信息或者用户资料的一种手段。

  • CRSF工作原理是什么?

    当你请求页面,如果页面源码中没有{% 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 %}
    <!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: "http://127.0.0.1:8001/index/",
                   type: 'get',
                   success: function (res) {
                       console.log(res)
                  }
              })
          })
       </script>
    </body>
    </html>

     

     

    研究一下报错原因:

    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})

 

posted @ 2020-07-01 16:40  牧羊小董  阅读(1039)  评论(0)    收藏  举报