Django高级csrf、cookie、session、ajax、form、middlewares

本文围绕Django展开,介绍了CSRF的简单用法,它能处理跨站请求伪造问题,提交POST数据时会进行校验。还阐述了COOKIE和session的工作原理及登录校验案例,讲解了AJAX请求设置csrf_token、Django内置序列化方法等。此外,介绍了Form组件、ModelForm的使用,以及Django中间件的登陆验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

csrf简单用法

什么是CSRF ?
跨站请求伪造,

问题:

  1. 钓鱼网站的页面和正经网站的页面对浏览器来说有什么区别? (页面是怎么来的?)
    钓鱼网站的页面是由 钓鱼网站的服务端给你返回的
    正经网站的网页是由 正经网站的服务端给你返回的

  2. Django中内置了一个专门处理csrf问题的中间件
    django.middleware.csrf.CsrfViewMiddleware
    当你提交POST数据的时候,它帮你做校验,如果校验不通过就拒绝这次请求
    功能:在render返回页面的时候,在页面中塞了一个隐藏的input标签

     <input type="hidden" name="csrfmiddlewaretoken" value="8gthvLKulM7pqulNl2q3u46v1oEbKG7BSwg6qsHBv4zf0zj0UcbQmpbAdijqyhfE">
     
     用法:我们在页面上 form表单 里面写上 {% csrf_token %}
    

COOKIE

Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。

cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。

获取Cookie
request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)	
参数:
default: 默认值
salt: 加密盐
max_age: 后台控制过期时间

设置Cookie
rep = HttpResponse(...)
rep = render(request, ...)
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)
参数:
key, 键
value='', 值
max_age=None, 超时时间
expires=None, 超时时间
path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
domain=None, Cookie生效的域名
secure=False, https传输
httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

删除Cookie
def logout(request):
	rep = redirect("/login/")
	rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
	return rep

登录校验案例

def check_login(func):
@wraps(func)
def inner(request, *args, **kwargs):
    next_url = request.get_full_path()
    if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":
        # 已经登录的用户...
        return func(request, *args, **kwargs)
    else:
        # 没有登录的用户,跳转刚到登录页面
        return redirect("/login/?next={}".format(next_url))
return inner

def login(request):
	if request.method == "POST":
		username = request.POST.get("username")
		passwd = request.POST.get("password")
		if username == "xxx" and passwd == "dashabi":
			next_url = request.GET.get("next")
			if next_url and next_url != "/logout/":
				response = redirect(next_url)
			else:
				response = redirect("/class_list/")
			response.set_signed_cookie("login", "yes", salt="SSS")
			return response
return render(request, "login.html")

@check_login
def home(request):
	return render(request, "home.html")

session

我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。

# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']

# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()

# 会话session的key
request.session.session_key

# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()

# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")

# 删除当前会话的所有Session数据
request.session.delete()
  
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush() 
	这用于确保前面的会话数据不可以再次被用户的浏览器访问
	例如,django.contrib.auth.logout() 函数中就会调用它。

# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
	* 如果value是个整数,session会在些秒数后失效。
	* 如果value是个datatime或timedelta,session就会在这个时间后失效。
	* 如果value是0,用户关闭浏览器session就会失效。
	* 如果value是None,session会依赖全局session失效策略。
	
# session校验案例
def check_login(func):
	@wraps(func)  # 装饰器修复技术
	def inner(request, *args, **kwargs):
		ret = request.session.get("is_login")
		# 1. 获取cookie中的随机字符串
		# 2. 根据随机字符串去数据库取 session_data --> 解密 --> 反序列化成字典
		# 3. 在字典里面 根据 is_login 取具体的数据
		if ret == "1":
			# 已经登陆过的 继续执行
			return func(request, *args, **kwargs)
		# 没有登录过的 跳转到登录页面
		else:
			# 获取当前访问的URL
			next_url = request.path_info
			print(next_url)
			return redirect("/app02/login/?next={}".format(next_url))
	return inner
	
def login(request):
	if request.method == "POST":
		user = request.POST.get("user")
		pwd = request.POST.get("pwd")
		# 从URL里面取到 next 参数
		next_url = request.GET.get("next")

		if user == "alex" and pwd == "dsb":
			# 登陆成功
			# 告诉浏览器保存一个键值对

			if next_url:
				rep = redirect(next_url)  # 得到一个响应对象
			else:
				rep = redirect("/app02/home/")  # 得到一个响应对象
			# 设置session
			request.session["is_login"] = "1"
			request.session["name"] = user
			request.session.set_expiry(7)  # 7秒钟之后失效
			return rep

	return render(request, "app02/login.html")

@check_login
def home(request):
	user = request.session.get("name")
	return render(request, "app02/home.html", {"user": user})

# 注销函数
def logout(request):
	# 只删除session数据
	# request.session.delete()
	# 如何删除session数据和cookie
	request.session.flush()
	return redirect("/app02/login/")

class怎样使用装饰器

1.加在CBV视图的get或post方法上
from django.utils.decorators import method_decorator
@method_decorator(check_login)
def post(self, request):
    print("Home View POST method...")
    return redirect("/index/")
	
2. 加在dispatch方法上,相当于给get和post方法都加上了登录校验。
from django.utils.decorators import method_decorator
@method_decorator(check_login)
def dispatch(self, request, *args, **kwargs):
    return super(HomeView, self).dispatch(request, *args, **kwargs)
	
3. 直接加在视图类上,但method_decorator必须传 name 关键字参数
from django.utils.decorators import method_decorator

@method_decorator(check_login, name="get")
@method_decorator(check_login, name="post")
class HomeView(View):
	def dispatch(self, request, *args, **kwargs):
		return super(HomeView, self).dispatch(request, *args, **kwargs)

	def get(self, request):
		return render(request, "home.html")

	def post(self, request):
		print("Home View POST method...")
		return redirect("/index/")

补充

CSRF Token相关装饰器在CBV只能加到dispatch方法上
或加在视图类上然后name参数指定为dispatch方法。

备注:
csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。
csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。
方法1
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator

class HomeView(View):
	@method_decorator(csrf_exempt)
	def dispatch(self, request, *args, **kwargs):
	...
	
方法2
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator

@method_decorator(csrf_exempt, name='dispatch')
class HomeView(View):
	...

AJAX

搜索引擎根据用户输入的关键字,自动提示检索关键字。
还有一个很重要的应用场景就是注册时候的用户名的查重。
优点:
	AJAX使用JavaScript技术向服务器发送异步请求;
	AJAX请求无须刷新整个页面;
	因为服务器响应内容是是页面中的部分内容,所以AJAX性能高; 
#案例
<input type="text" id="i1">+
<input type="text" id="i2">=
<input type="text" id="i3">
<input type="button" value="AJAX提交"  id="b1">

<script>
	//定位b1的点击操作,执行function
	$("#b1").on("click", function () {
		var i1 = $("#i1").val();
		//通过val()方法取值
		var i2 = $("#i2").val();
		// 往后端发数据
		$.ajax({
			url: "/ajax_add/",
			type: "get",
			data: {"i1": i1, "i2": i2},
			success: function (arg) {					
				// 把返回的结果填充到 id是i3的input框中
				$("#i3").val(arg);
			}
		})
	});
</script>

AJAX请求如何设置csrf_token

$.ajax({
  url: "/cookie_ajax/",
  type: "POST",
  data: {
	"username": "Q1mi",
	"password": 123456,
	"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()  // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中
  },
  success: function (data) {
	console.log(data);
  }
})

$.ajax({
  url: "/cookie_ajax/",
  type: "POST",
  headers: {"X-CSRFToken": $.cookie('csrftoken')},  // 从Cookie取csrftoken,并设置到请求头中
  data: {"username": "Q1mi", "password": 123456},
  success: function (data) {
	console.log(data);
  }
})

#编写setupajax.js文件,使用Ajax前导入介绍文件就行
<script src="/static/setupajax.js"></script>
文件代码如下
function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function (xhr, settings) {
	if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
	  xhr.setRequestHeader("X-CSRFToken", csrftoken);
	}
  }
});

Django内置的serializers序列化方法

def books_json(request):
	book_list = models.Book.objects.all()[0:10]
	from django.core import serializers
	ret = serializers.serialize("json", book_list)
	return HttpResponse(ret)

Ajax文件上传

// 上传文件示例
$("#b3").click(function () {
  var formData = new FormData();
  formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
  formData.append("f1", $("#f1")[0].files[0]);
  $.ajax({
    url: "/upload/",
    type: "POST",
    processData: false,  // 告诉jQuery不要去处理发送的数据
    contentType: false,  // 告诉jQuery不要去设置Content-Type请求头
    data: formData,
    success:function (data) {
      console.log(data)
    }
  })
})

JS 操作替代删除提示框

地址https://github.com/lipis/bootstrap-sweetalert
HTML文件
删除

<script src="/static/sweetalert/sweetalert.min.js"></script>
<script>
	// 找到删除按钮绑定事件,button的class属性del
	$(".del").on("click", function () {
		var $trEle = $(this).parent().parent();
		var delId = $trEle.children().eq(1).text();

		swal({
		  title: "你确定要删除吗?",
		  text: "一旦删除就找不回来了",
		  type: "warning",
		  showCancelButton: true,
		  confirmButtonClass: "btn-warning",
		  confirmButtonText: "确认",
		  cancelButtonText: "取消",
		  closeOnConfirm: false,
		},
		function(){
			// 向后端发送删除的请求
			$.ajax({
				url: "/delete/",
				type: "post",
				data: {"id":delId},
				success:function (arg) {
					swal(arg, "你可以跑路了!", "success");
					$trEle.remove();
				}
			});

		});
	})

</script>
路由
url(r'^delete/$', views.delete),

视图
def delete(request):
	del_id = request.POST.get("id")
	print(del_id)
	models.Person.objects.filter(id=del_id).delete()
	return HttpResponse("删除成功!")

Form组件

views.py先定义好一个RegForm类:

Field
required=True,               是否允许为空
widget=None,                 HTML插件
label=None,                  用于生成Label标签或显示内容
initial=None,                初始值
help_text='',                帮助信息(在标签旁边显示)
error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
validators=[],               自定义验证规则
localize=False,              是否支持本地化
disabled=False,              是否可以编辑
label_suffix=None            Label内容后缀

from django import forms
username = forms.CharField(min_length=8,label="用户名",initial="张三" #设置默认值)

error_messages		重写错误信息

password		密码隐藏
from django.forms import widgets
pwd = forms.CharField(
    min_length=6,
    label="密码",
    widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)

radioSelect		单radio值为字符串
gender = forms.fields.ChoiceField(
    choices=((1, "男"), (2, "女"), (3, "保密")),
    label="性别",
    initial=3,
    widget=forms.widgets.RadioSelect()
)

单选Select
hobby = forms.fields.ChoiceField(
    choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
    label="爱好",
    initial=3,
    widget=forms.widgets.Select()
)

多选Select
hobby = forms.fields.MultipleChoiceField(
    choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
    label="爱好",
    initial=[1, 3],
    widget=forms.widgets.SelectMultiple()
)

单选checkbox
keep = forms.fields.ChoiceField(
    label="是否记住密码",
    initial="checked",
    widget=forms.widgets.CheckboxInput()
)

多选checkbox
hobby = forms.fields.MultipleChoiceField(
    choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
    label="爱好",
    initial=[1, 3],
    widget=forms.widgets.CheckboxSelectMultiple()
)

choice字段注意事项	在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
from django.forms import Form
from django.forms import widgets
from django.forms import fields
 
class MyForm(Form):
 
	user = fields.ChoiceField(
		# choices=((1, '上海'), (2, '北京'),),
		initial=2,
		widget=widgets.Select
	)	 
	def __init__(self, *args, **kwargs):
		super(MyForm,self).__init__(*args, **kwargs)
		self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')

完整案例

from django import forms
# 按照Django form组件的要求自己写一个类
from django import forms
class RegForm(forms.Form):
	name = forms.CharField(max_length=16,label="用户名",error_messages={"required""该字段不能为空",},)
	pwd = forms.CharField(min_length=6,label="密码",error_messages={
        "min_length": "密码不能少于6位!","required": "该字段不能为空",})

视图函数
# 使用form组件实现注册方式
def reg(request):
	form_obj = RegForm()		
	if request.method == "POST":
		# 实例化form对象的时候,把post提交过来的数据直接传进去
		form_obj = RegForm(request.POST)
		# 调用form_obj校验数据的方法
		if form_obj.is_valid():
			return HttpResponse("注册成功")
	return render(request, "register.html", {"form_obj": form_obj})
	
HTML文件
#第一种
<form action="/reg/" method="post" novalidate>
	{% csrf_token %}
	//显示表单
	{{ form_obj.as_p }}
	//显示错误信息
	{{ form_obj.errors }}
	<p><input type="submit"></p>
</form>

#第二种
<form action="" method="post" novalidate>
 {% csrf_token %}
 {% for field in form %}
    <div>
     {{ field.label }}
     {{ field }}
     {{ field.errors }}
    </div>
 {% endfor %}
<input type="submit">

# 第三种,自己写HTML
<form action="/reg2/" method="post" novalidate>
	{% csrf_token %}
	<div class="form-group {% if form_obj.name.errors.0 %}has-error{% endif %}">
		{{ form_obj.name.label }}
		{{ form_obj.name }}
		<span class="help-block">{{ form_obj.name.errors.0 }}</span>
	</div>

	<div class="form-group {% if form_obj.pwd.errors.0 %}has-error{% endif %}">
		{{ form_obj.pwd.label }}
		{{ form_obj.pwd }}
		<span class="help-block">{{ form_obj.pwd.errors.0 }}</span>
	</div>
 </form>

form校验RegexValidator验证器,通过form_obj.is_valid()校验

from django.core.validators import RegexValidator
mobile = forms.CharField(
	label="手机",
	# 自己定制校验规则
	validators=[
		RegexValidator(r'^[0-9]+$', '手机号必须是数字'),
		RegexValidator(r'^1[3-9][0-9]{9}$', '手机格式有误')
	],		
	error_messages={
		"required": "该字段不能为空",
	}
)

form校验自定义方法

1局部钩子	在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
 # 定义局部钩子,用来校验username字段
	def clean_username(self):
		value = self.cleaned_data.get("username")
		if "666" in value:
			raise ValidationError("光喊666是不行的")
		else:
			return value
			
2全局钩子	在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验。
 def clean(self):
	pwd = self.cleaned_data.get("pwd")
    re_pwd = self.cleaned_data.get("re_pwd")
    if pwd != re_pwd:
        self.add_error("re_pwd", ValidationError("两次密码不一致"))
        raise ValidationError("两次密码不一致")
    return self.cleaned_data

ModelForm

你也许会有个Book模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中

class BookForm(forms.ModelForm):
	class Meta:
		model = models.Book
		fields = "__all__"
		labels = {
			"title": "书名",
			"price": "价格"
		}
		widgets = {
			"password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
		}

常用参数

model = models.Book  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息

每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:

from myapp.models import Book
from myapp.forms import BookForm

# 根据POST数据创建一个新的form对象
form_obj = BookForm(request.POST)
# 新建书籍对象,并存储
new_ book = form_obj.save()

#基于一个书籍对象创建form对象
edit_obj = Book.objects.get(id=1)
# 使用POST提交的数据更新书籍对象的数据
form_obj = BookForm(request.POST, instance=edit_obj)
form_obj.save()

Django中间件

官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出每个中间件组件都负责做一些特定的功能。
自定义中间件

中间件可以定义五个方法,分别是:(主要的是process_request和process_response)
process_request(self,request)
它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。
总结
	1.中间件的process_request方法是在执行视图函数之前执行的。
	2.当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
	3.不同中间件之间传递的request都是同一个对象

process_view(self, request, view_func, view_args, view_kwargs)
它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。

process_template_response(self,request,response)
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。顺序是倒序。

process_exception(self, request, exception)
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。

process_response(self, request, response)
	1.该方法的返回值也必须是HttpResponse对象。
	2.多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,

以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。

中间件版登陆验证

urls.py

from django.conf.urls import url
from app01 import views

urlpatterns = [
	url(r'^index/$', views.index),
	url(r'^login/$', views.login, name='login'),
]

views.py

from django.shortcuts import render, HttpResponse, redirect

def index(request):
	return HttpResponse('this is index')

def home(request):
	return HttpResponse('this is home')

def login(request):
	if request.method == "POST":
		user = request.POST.get("user")
		pwd = request.POST.get("pwd")

		if user == "Q1mi" and pwd == "123456":
			# 设置session
			request.session["user"] = user
			# 获取跳到登陆页面之前的URL
			next_url = request.GET.get("next")
			# 如果有,就跳转回登陆之前的URL
			if next_url:
				return redirect(next_url)
			# 否则默认跳转到index页面
			else:
				return redirect("/index/")
	#get请求就访问登录页面
	return render(request, "login.html")

login.html

<!DOCTYPE html>
<html lang="en">
<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>登录页面</title>
</head>
<body>
<form action="{% url 'login' %}">
	<p>
		<label for="user">用户名:</label>
		<input type="text" name="user" id="user">
	</p>
	<p>
		<label for="pwd">密 码:</label>
		<input type="text" name="pwd" id="pwd">
	</p>
	<input type="submit" value="登录">
</form>
</body>
</html>

middlewares.py

class AuthMD(MiddlewareMixin):
	white_list = ['/login/', ]  # 白名单
	balck_list = ['/black/', ]  # 黑名单

	def process_request(self, request):
		from django.shortcuts import redirect, HttpResponse
		#获取登陆页面的路径
		next_url = request.path_info
		print(request.path_info, request.get_full_path())

		if next_url in self.white_list or request.session.get("user"):
			return
		elif next_url in self.balck_list:
			return HttpResponse('This is an illegal URL')
		else:
			return redirect("/login/?next={}".format(next_url))

settings.py注册

MIDDLEWARE = [
	'django.middleware.security.SecurityMiddleware',
	'django.contrib.sessions.middleware.SessionMiddleware',
	'django.middleware.common.CommonMiddleware',
	'django.middleware.csrf.CsrfViewMiddleware',
	'django.contrib.auth.middleware.AuthenticationMiddleware',
	'django.contrib.messages.middleware.MessageMiddleware',
	'middlewares.AuthMD',
]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值